Skip to content

Instantly share code, notes, and snippets.

@nestarz
Created September 6, 2021 22:16
Show Gist options
  • Save nestarz/219e548a54cfbf301ed0fd670769f920 to your computer and use it in GitHub Desktop.
Save nestarz/219e548a54cfbf301ed0fd670769f920 to your computer and use it in GitHub Desktop.
Remove Unused Dependencies
import acorn from "acorn";
import glob from "glob";
import esbuild from "esbuild";
import fs from "fs/promises";
import { resolve, dirname } from "path";
const isNode = (node) => typeof node?.type === "string";
const getChilds = (node) =>
Object.keys(node)
.filter((key) => node[key] && key !== "parent")
.flatMap((key) => node[key])
.filter(isNode);
export function* walk({ node, parent } = {}) {
for (const child of getChilds(node)) {
yield* walk({ node: child, parent: node });
}
yield { node, parent };
}
export const walker = (source, parser = acorn, options) =>
walk({
node: parser.parse(source, {
sourceType: "module",
ecmaVersion: 11,
...options,
}),
});
export const extractImportSource = (node) => {
const sources = {
ImportDeclaration: (node) =>
node.importKind !== "type" && node?.source?.value,
ExportNamedDeclaration: (node) => node?.source?.value,
ExportAllDeclaration: (node) => node?.source?.value,
CallExpression: (node) =>
node.callee.type === "Import" &&
node.arguments.length &&
node.arguments[0].value,
};
const getDependencyFn = sources[node.type];
return getDependencyFn ? getDependencyFn(node) : null;
};
export const updateDependencies = async (body, updateFn) => {
const nodes = walker(body);
return Array.from(nodes)
.reduce(async (chunksP, { node }) => {
const chunks = await chunksP;
const dependency = extractImportSource(node);
if (dependency && dependency.startsWith(".")) {
chunks[node.start] = chunks
.slice(node.start, node.end)
.join("")
.replace(dependency, await updateFn(dependency));
for (let i = node.start + 1; i < node.end; i++) {
chunks[i] = "";
}
}
return chunks;
}, Promise.resolve(body.split("")))
.then((str) => str.join(""));
};
const esbuildTransform = async (string, loader) => {
const js = await esbuild.transform(string, { loader });
return js.code;
};
export const transform = {
jsx: (jsx) => esbuildTransform(jsx, "jsx"),
tsx: (tsx) => esbuildTransform(tsx, "tsx"),
ts: (ts) => esbuildTransform(ts, "ts"),
};
export default walker;
const findPath = async (base) => {
let next = base;
const set = (base, ext) => {
next = base + ext;
return next;
};
await fs
.readFile(base)
.catch(() => fs.readFile(set(base, ".js")))
.catch(() => fs.readFile(set(base, ".jsx")))
.catch(() => fs.readFile(set(base, "/index.jsx")));
return next;
};
const seen = new Set();
const getDependencies = async (path, depth = 0) => {
seen.add(resolve(path));
const source = await fs.readFile(path);
const files = new Set();
await updateDependencies(await transform.jsx(String(source)), async (v) => {
if (
![".json", ".svg", ".png", ".jpg", ".scss", ".css"].some((a) =>
v.includes(a)
)
) {
const next = await findPath(resolve(dirname(path), v.split("?")[0]));
if (!seen.has(next)) files.add(next);
}
});
for (const v of files) {
await getDependencies(v, depth + 1);
seen.add(v);
}
return seen;
};
getDependencies("src/App.jsx").then((seen) => {
["js", "jsx"].forEach((ext) =>
glob("./src/**/*." + ext, {}, (err, files) =>
files
.map((a) => resolve(a))
.forEach((file) => {
if (!seen.has(file)) fs.rm(file);
})
)
);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment