UNPKG

4.91 kBJavaScriptView Raw
1const path = require("path");
2const chalk = require("chalk");
3const fg = require("fast-glob");
4
5function detectDeadCode(compilation, options) {
6 const assets = getWebpackAssets(compilation);
7 const compiledFiles = convertFilesToDict(assets);
8 const includedFiles = fg.sync(getPattern(options));
9
10 let unusedFiles = [];
11 let unusedExportMap = [];
12
13 if (options.detectUnusedFiles) {
14 unusedFiles = includedFiles.filter((file) => !compiledFiles[file]);
15
16 if (Object.keys(unusedFiles).length > 0 || options.log === "all") {
17 logUnusedFiles(unusedFiles);
18 }
19 }
20
21 if (options.detectUnusedExport) {
22 unusedExportMap = getUsedExportMap(convertFilesToDict(includedFiles), compilation);
23
24 if (Object.keys(unusedExportMap).length > 0 || options.log == "all") {
25 logUnusedExportMap(unusedExportMap);
26 }
27 }
28
29 if (unusedFiles.length > 0 || unusedExportMap.length > 0) {
30 if (options.failOnHint) {
31 process.exit(2);
32 }
33 }
34}
35
36function getPattern({ context, patterns, exclude }) {
37 return patterns
38 .map((pattern) => path.resolve(context, pattern))
39 .concat(exclude.map((pattern) => `!${pattern}`))
40 .map(convertToUnixPath);
41}
42
43function getUsedExportMap(includedFileMap, compilation) {
44 const unusedExportMap = {};
45
46 compilation.chunks.forEach(function (chunk) {
47 const isWebpack5 = compilation.chunkGraph ? true : false;
48
49 for (const module of chunk.modulesIterable) {
50 if (!module.resource) continue;
51
52 let providedExports;
53 if (isWebpack5) {
54 providedExports = compilation.chunkGraph.moduleGraph.getProvidedExports(module);
55 } else {
56 providedExports = module.providedExports || module.buildMeta.providedExports;
57 }
58
59 let usedExports;
60 if (isWebpack5) {
61 usedExports = compilation.chunkGraph.moduleGraph.getUsedExports(module, chunk.runtime);
62 } else {
63 usedExports = module.usedExports;
64 }
65
66 const path = convertToUnixPath(module.resource);
67 let usedExportsArr = [];
68 // in webpack 4 usedExports can be null | boolean | Array<string>
69 // in webpack 5 it can be null | boolean | SortableSet<string>
70 if (usedExports instanceof Set) {
71 usedExportsArr = Array.from(usedExports);
72 } else {
73 usedExportsArr = usedExports;
74 }
75
76 if (usedExports !== true && providedExports !== true && /^((?!(node_modules)).)*$/.test(path) && includedFileMap[path]) {
77 if (usedExports === false) {
78 unusedExportMap[path] = providedExports;
79 } else if (providedExports instanceof Array) {
80 const unusedExports = providedExports.filter((x) => usedExportsArr instanceof Array && !usedExportsArr.includes(x));
81
82 if (unusedExports.length > 0) {
83 unusedExportMap[path] = unusedExports;
84 }
85 }
86 }
87 }
88 });
89 return unusedExportMap;
90}
91
92function logUnusedExportMap(unusedExportMap) {
93 console.log(chalk.yellow("\n--------------------- Unused Exports ---------------------"));
94 if (Object.keys(unusedExportMap).length > 0) {
95 let numberOfUnusedExport = 0;
96
97 Object.keys(unusedExportMap).forEach((modulePath) => {
98 const unusedExports = unusedExportMap[modulePath];
99
100 console.log(chalk.yellow(`\n${modulePath}`));
101 console.log(chalk.yellow(` ⟶ ${unusedExports.join(", ")}`));
102 numberOfUnusedExport += unusedExports.length;
103 });
104 console.log(chalk.yellow(`\nThere are ${numberOfUnusedExport} unused exports (¬º-°)¬.\n`));
105 } else {
106 console.log(chalk.green("\nPerfect, there is nothing to do ٩(◕‿◕。)۶."));
107 }
108}
109
110function getWebpackAssets(compilation) {
111 let assets = Array.from(compilation.fileDependencies);
112
113 Object.keys(compilation.assets).forEach((assetName) => {
114 const assetPath = compilation.assets[assetName].existsAt;
115
116 assets.push(assetPath);
117 });
118 return assets;
119}
120
121function convertFilesToDict(assets) {
122 return assets
123 .filter((file) => file && file.indexOf("node_modules") === -1)
124 .reduce((acc, file) => {
125 const unixFile = convertToUnixPath(file);
126
127 acc[unixFile] = true;
128 return acc;
129 }, {});
130}
131
132function logUnusedFiles(unusedFiles) {
133 console.log(chalk.yellow("\n--------------------- Unused Files ---------------------"));
134 if (unusedFiles.length > 0) {
135 unusedFiles.forEach((file) => console.log(`\n${chalk.yellow(file)}`));
136 console.log(
137 chalk.yellow(`\nThere are ${unusedFiles.length} unused files (¬º-°)¬.`),
138 chalk.red.bold(`\n\nPlease be careful if you want to remove them.\n`)
139 );
140 } else {
141 console.log(chalk.green("\nPerfect, there is nothing to do ٩(◕‿◕。)۶."));
142 }
143}
144
145function convertToUnixPath(path) {
146 return path.replace(/\\+/g, "/");
147}
148module.exports = detectDeadCode;