unplugin-unused
Version:
Check unused dependencies.
93 lines (90 loc) • 3.08 kB
JavaScript
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 };