1 | const path = require("path");
|
2 | const chalk = require("chalk");
|
3 | const fg = require("fast-glob");
|
4 |
|
5 | function 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 |
|
36 | function 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 |
|
43 | function 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 |
|
69 |
|
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 |
|
92 | function 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 |
|
110 | function 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 |
|
121 | function 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 |
|
132 | function 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 |
|
145 | function convertToUnixPath(path) {
|
146 | return path.replace(/\\+/g, "/");
|
147 | }
|
148 | module.exports = detectDeadCode;
|