UNPKG

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
//#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;