vite-plugin-unused-code
Version:
Vite/Rollup plugin to detect unused files and unused exports in used files
171 lines (164 loc) • 6.43 kB
JavaScript
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) {
__defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
}
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let fs = require("fs");
fs = __toESM(fs);
let path = require("path");
path = __toESM(path);
let fast_glob = require("fast-glob");
fast_glob = __toESM(fast_glob);
let micromatch = require("micromatch");
micromatch = __toESM(micromatch);
//#region src/bundler.ts
function cleanupFilePath(id) {
const searchIndex = id.indexOf("?");
return searchIndex === -1 ? id : id.slice(0, searchIndex);
}
function diff(arr1, arr2) {
return [...arr1.filter((item) => arr2.includes(item)), ...arr2.filter((item) => arr1.includes(item))];
}
function getModules(bundle, transformedModules) {
const modules = /* @__PURE__ */ new Map();
for (const chunk of Object.values(bundle)) {
const renderedModules = chunk.type === "chunk" ? chunk.modules : {};
for (const [id, data] of Object.entries(renderedModules)) {
const file = cleanupFilePath(id);
if (path.isAbsolute(file)) {
const key = path.normalize(file);
const existing = modules.get(key);
const removedExports = (data.removedExports ?? []).filter((name) => name !== "__esModule");
modules.set(key, { removedExports: existing ? diff(existing.removedExports ?? [], removedExports) : removedExports });
}
}
}
if (transformedModules) {
for (const key of transformedModules) if (!modules.has(key)) modules.set(key, {});
}
return modules;
}
//#endregion
//#region src/glob.ts
function searchGlobs(globs, cwd = process.cwd()) {
const files = fast_glob.default.sync(globs, {
absolute: true,
cwd
});
/**
* `fast-glob` will return absolute paths in POSIX style on Windows
* {@link https://github.com/micromatch/micromatch?tab=readme-ov-file#backslashes}
*/
return process.platform === "win32" ? files.map((file) => path.normalize(file)) : files;
}
function filterGlobs(files, globs, cwd = process.cwd()) {
return (0, micromatch.default)(files.map((file) => path.relative(cwd, file)), globs, { cwd }).map((file) => path.normalize(path.join(cwd, file)));
}
//#endregion
//#region src/index.ts
function generateUnusedFilesMessage(unusedFiles) {
const numberOfUnusedFile = unusedFiles.length;
const shouldUsePlural = numberOfUnusedFile > 1;
return `
--------------------- Unused Files ---------------------
${numberOfUnusedFile ? `
${unusedFiles.join("\n\n")}
There are ${numberOfUnusedFile} unused file${shouldUsePlural ? "s" : ""}. Please be careful if you want to remove ${shouldUsePlural ? "them" : "it"}.
` : `Perfect, there is nothing to do.`}`;
}
function generateUnusedExportsMessage(unusedExports) {
const numberOfUnusedExport = unusedExports.map(([file, exports$1]) => exports$1.length).reduce((a, b) => a + b, 0);
const shouldUsePlural = numberOfUnusedExport > 1;
return `
--------------------- Unused Exports ---------------------
${numberOfUnusedExport ? `${unusedExports.map(([file, exports$1]) => `${file}
⟶ ${exports$1.join(", ")}`).join("\n\n")}
There are ${numberOfUnusedExport} unused export${shouldUsePlural ? "s" : ""}. Please be careful if you want to remove ${shouldUsePlural ? "them" : "it"}.
` : `Perfect, there is nothing to do.`}`;
}
async function ensureFile(file) {
try {
await fs.promises.mkdir(path.dirname(file), { recursive: true });
} catch {}
}
async function writeJSON(file, data) {
await ensureFile(file);
await fs.promises.writeFile(file, JSON.stringify(data, null, 2) + "\n");
}
const unusedCodePlugin = (customOptions) => {
const options = {
patterns: ["**/*.*"],
exclude: [],
detectUnusedFiles: true,
detectUnusedExport: true,
exportJSON: false,
failOnHint: false,
...customOptions
};
const transformedModules = /* @__PURE__ */ new Set();
return {
enforce: "post",
apply: "build",
name: "vite-plugin-unused-code",
configResolved(config) {
options.context ??= config.root;
options.log ??= config.logLevel === "silent" ? "none" : config.logLevel === "info" ? "all" : "unused";
},
transform(code, id) {
const file = cleanupFilePath(id);
if (path.isAbsolute(file)) transformedModules.add(path.normalize(file));
},
generateBundle(outputOptions, bundle) {
const { context = process.cwd(), patterns, exclude, detectUnusedFiles, detectUnusedExport, log = "all", exportJSON, failOnHint } = options;
const globs = patterns.concat(exclude.map((pattern) => `!${pattern}`));
const modules = getModules(bundle, transformedModules);
let unusedFiles = [];
let unusedExports = [];
if (detectUnusedFiles) {
unusedFiles = searchGlobs(globs, context).filter((file) => !modules.has(file));
if (log === "all" || log === "unused" && unusedFiles.length) this.warn(generateUnusedFilesMessage(unusedFiles));
}
if (detectUnusedExport) {
unusedExports = filterGlobs([...modules.keys()], globs, context).filter((file) => {
return modules.get(file).removedExports?.length;
}).map((file) => {
return [file, modules.get(file).removedExports];
});
if (log === "all" || log === "unused" && unusedExports.length) this.warn(generateUnusedExportsMessage(unusedExports));
}
if (exportJSON) {
let exportPath = "unused-code.json";
if (typeof exportJSON === "string") exportPath = path.join(exportJSON, exportPath);
writeJSON(exportPath, {
files: unusedFiles,
exports: unusedExports
});
}
if (failOnHint && (unusedFiles.length > 0 || unusedExports.length > 0)) this.error("Unused code detected.");
}
};
};
var src_default = unusedCodePlugin;
//#endregion
module.exports = src_default;