Created
September 6, 2021 22:16
-
-
Save nestarz/219e548a54cfbf301ed0fd670769f920 to your computer and use it in GitHub Desktop.
Remove Unused Dependencies
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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