unplugin-unused
Version:
Check unused dependencies.
84 lines (82 loc) • 2.87 kB
JavaScript
import { t as resolveOptions } from "./options-DJtl1Tg1.mjs";
import { readFile } from "node:fs/promises";
import { styleText } from "node:util";
import { up } from "empathic/package";
import escapeStringRegexp from "escape-string-regexp";
import jsTokens from "js-tokens";
import { createUnplugin } from "unplugin";
//#region src/index.ts
const Unused = createUnplugin((rawOptions = {}) => {
const options = resolveOptions(rawOptions);
const depsRegex = {};
const depsState = /* @__PURE__ */ 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;
}
return {
name: "unplugin-unused",
enforce: "pre",
async buildStart() {
const pkgJsonPath = up({ cwd: options.root });
if (!pkgJsonPath) throw new Error(`Cannot find package.json from root: ${options.root}`);
pkgPath = pkgJsonPath;
const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
const deps = /* @__PURE__ */ new Set();
for (const kind of options.depKinds) {
const dependencies = Object.keys(pkg[kind] || {});
for (const dep of dependencies) {
if ((Array.isArray(options.ignore) ? options.ignore : options.ignore[kind] || []).includes(dep) || deps.has(dep)) continue;
deps.add(dep);
depsRegex[dep] = new RegExp(String.raw`["']${escapeStringRegexp(dep)}['"\/]`);
}
}
const buildId = getBuildId(this);
depsState.set(buildId, deps);
},
transform: {
filter: { id: {
include: options.include,
exclude: options.exclude
} },
handler(code, id) {
const tokens = jsTokens(code, { jsx: /\.[jt]sx?$/.test(id) });
const deps = depsState.get(getBuildId(this)) || /* @__PURE__ */ new Set();
for (const { type, value } of tokens) {
if (type.endsWith("Comment")) continue;
for (const dep of deps) if (depsRegex[dep].test(value)) {
deps.delete(dep);
break;
}
}
}
},
buildEnd(...args) {
if (!!args[0]) return;
const id = getBuildId(this);
const deps = depsState.get(id);
if (deps?.size) {
const message = `Unused ${styleText("cyan", String(deps.size))} dependencies found: \n\n${Array.from(deps).map((dep) => `- ${styleText("bold", dep)}`).join("\n")}\n\nYou can remove them from ${pkgPath}`;
if (options.level === "error") throw new Error(String(styleText("red", message)));
else {
const error = new Error(styleText("yellow", message));
console.warn(error);
}
}
},
vite: {
apply: "build",
configResolved(config) {
options.root ||= config.root;
}
}
};
});
//#endregion
export { Unused };