UNPKG

unplugin-unused

Version:
93 lines (90 loc) 3.08 kB
import { resolveOptions } from "./options-B-lSSenG.js"; import path from "node:path"; import process from "node:process"; import jsTokens from "js-tokens"; import pc from "picocolors"; import { readPackageJSON, resolvePackageJSON } from "pkg-types"; import { createUnplugin } from "unplugin"; import { createFilter } from "unplugin-utils"; //#region node_modules/.pnpm/escape-string-regexp@5.0.0/node_modules/escape-string-regexp/index.js function escapeStringRegexp(string) { if (typeof string !== "string") throw new TypeError("Expected a string"); return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d"); } //#endregion //#region src/index.ts const Unused = createUnplugin((rawOptions = {}) => { const options = resolveOptions(rawOptions); const filter = createFilter(options.include, options.exclude); const depsRegex = {}; const depsState = new WeakMap(); let pkgPath; const defaultKey = {}; function getBuildId(context) { const native = context.getNativeBuildContext?.(); if (!native) return defaultKey; if (native.framework === "esbuild") return native.build; if (native.framework === "rspack" || native.framework === "webpack") return native.compiler; if (native.framework === "farm") return native.context; return defaultKey; } const name = "unplugin-unused"; return { name, enforce: "pre", async buildStart() { options.root ||= process.cwd(); pkgPath = path.resolve(await resolvePackageJSON(options.root)); const pkg = await readPackageJSON(pkgPath); const deps = new Set(); for (const kind of options.depKinds) { const dependencies = Object.keys(pkg[kind] || {}); for (const dep of dependencies) { const ignore = Array.isArray(options.ignore) ? options.ignore : options.ignore[kind] || []; if (ignore.includes(dep) || deps.has(dep)) continue; deps.add(dep); depsRegex[dep] = new RegExp(`["']${escapeStringRegexp(dep)}['"\\/]`); } } const buildId = getBuildId(this); depsState.set(buildId, deps); }, transformInclude(id) { return filter(id); }, transform(code, id) { const tokens = jsTokens(code, { jsx: /\.[jt]sx?$/.test(id) }); const deps = depsState.get(getBuildId(this)) || new Set(); for (const { type, value } of tokens) { if (type.endsWith("Comment")) continue; for (const dep of deps) { const regex = depsRegex[dep]; if (regex.test(value)) { deps.delete(dep); break; } } } }, buildEnd() { const id = getBuildId(this); const deps = depsState.get(id); if (deps?.size) { const message = `Unused ${pc.cyan(deps.size)} dependencies found: \n\n` + `${Array.from(deps).map((dep) => `- ${pc.bold(dep)}`).join("\n")}\n\n` + `You can remove them from ${pkgPath}`; if (options.level === "error") throw new Error(String(pc.red(message))); else { const error = new Error(pc.yellow(message)); console.warn(error); } } }, vite: { apply: "build", configResolved(config) { options.root ||= config.root; } } }; }); //#endregion export { Unused };