wxt
Version:
⚡ Next-gen Web Extension Framework
179 lines (178 loc) • 5.87 kB
JavaScript
import { parseModule } from "magicast";
export function removeMainFunctionCode(code) {
const mod = parseModule(code);
emptyMainFunction(mod);
let removedCount = 0;
let depth = 0;
const maxDepth = 10;
do {
removedCount = 0;
removedCount += removeUnusedTopLevelVariables(mod);
removedCount += removeUnusedTopLevelFunctions(mod);
removedCount += removeUnusedImports(mod);
} while (removedCount > 0 && depth++ <= maxDepth);
removeSideEffectImports(mod);
return mod.generate();
}
function emptyMainFunction(mod) {
if (mod.exports?.default?.$type === "function-call") {
if (mod.exports.default.$ast?.arguments?.[0]?.body) {
delete mod.exports.default.$ast.arguments[0];
} else if (mod.exports.default.$ast?.arguments?.[0]?.properties) {
mod.exports.default.$ast.arguments[0].properties = mod.exports.default.$ast.arguments[0].properties.filter(
(prop) => prop.key.name !== "main"
);
}
}
}
function removeUnusedTopLevelVariables(mod) {
const simpleAst = getSimpleAstJson(mod.$ast);
const usedMap = findUsedIdentifiers(simpleAst);
let deletedCount = 0;
const ast = mod.$ast;
const isUsed = (id) => {
return id?.type === "Identifier" && usedMap.get(id.name);
};
const cleanArrayPattern = (pattern) => {
const elements = pattern.elements;
for (let i = elements.length - 1; i >= 0; i--) {
const el = elements[i];
if (el?.type === "Identifier" && !isUsed(el)) {
elements.splice(i, 1);
deletedCount++;
}
}
return elements.length === 0;
};
const cleanObjectPattern = (pattern) => {
const properties = pattern.properties;
for (let i = properties.length - 1; i >= 0; i--) {
const prop = properties[i];
if (prop.type === "Property") {
const value = prop.value;
if (value.type === "ObjectPattern") {
const isEmpty = cleanObjectPattern(value);
if (isEmpty) {
properties.splice(i, 1);
}
} else if (value.type === "ArrayPattern") {
const isEmpty = cleanArrayPattern(value);
if (isEmpty) {
properties.splice(i, 1);
}
} else if (value.type === "Identifier" && !isUsed(value)) {
properties.splice(i, 1);
deletedCount++;
}
} else if (prop.type === "RestElement") {
const arg = prop.argument;
if (arg.type === "Identifier" && !isUsed(arg)) {
properties.splice(i, 1);
deletedCount++;
}
}
}
return properties.length === 0;
};
for (let i = ast.body.length - 1; i >= 0; i--) {
if (ast.body[i].type !== "VariableDeclaration") continue;
for (let j = ast.body[i].declarations.length - 1; j >= 0; j--) {
const id = ast.body[i].declarations[j].id;
let shouldRemove = false;
if (id.type === "Identifier") {
shouldRemove = !isUsed(id);
if (shouldRemove) deletedCount++;
} else if (id.type === "ArrayPattern") {
shouldRemove = cleanArrayPattern(id);
} else if (id.type === "ObjectPattern") {
shouldRemove = cleanObjectPattern(id);
}
if (shouldRemove) {
ast.body[i].declarations.splice(j, 1);
}
}
if (ast.body[i].declarations.length === 0) {
ast.body.splice(i, 1);
}
}
return deletedCount;
}
function removeUnusedTopLevelFunctions(mod) {
const simpleAst = getSimpleAstJson(mod.$ast);
const usedMap = findUsedIdentifiers(simpleAst);
let deletedCount = 0;
const ast = mod.$ast;
for (let i = ast.body.length - 1; i >= 0; i--) {
if (ast.body[i].type === "FunctionDeclaration" && !usedMap.get(ast.body[i].id.name)) {
ast.body.splice(i, 1);
deletedCount++;
}
}
return deletedCount;
}
function removeUnusedImports(mod) {
const simpleAst = getSimpleAstJson(mod.$ast);
const usedMap = findUsedIdentifiers(simpleAst);
const importSymbols = Object.keys(mod.imports);
let deletedCount = 0;
importSymbols.forEach((name) => {
if (usedMap.get(name)) return;
delete mod.imports[name];
deletedCount++;
});
return deletedCount;
}
function findUsedIdentifiers(simpleAst) {
const usedMap = /* @__PURE__ */ new Map();
const queue = [simpleAst];
for (const item of queue) {
if (!item) {
} else if (Array.isArray(item)) {
queue.push(...item);
} else if (item.type === "ImportDeclaration") {
continue;
} else if (item.type === "Identifier") {
usedMap.set(item.name, true);
} else if (typeof item === "object") {
const filterFns = {
// Ignore the function declaration's name
FunctionDeclaration: ([key]) => key !== "id",
// Ignore object property names
ObjectProperty: ([key]) => key !== "key",
// Ignore variable declaration's name
VariableDeclarator: ([key]) => key !== "id"
};
queue.push(
Object.entries(item).filter(filterFns[item.type] ?? (() => true)).map(([_, value]) => value)
);
}
}
return usedMap;
}
function deleteImportAst(mod, shouldDelete) {
const importIndexesToDelete = [];
mod.$ast.body.forEach((node, index) => {
if (node.type === "ImportDeclaration" && shouldDelete(node)) {
importIndexesToDelete.push(index);
}
});
importIndexesToDelete.reverse().forEach((i) => {
delete mod.$ast.body[i];
});
}
function removeSideEffectImports(mod) {
deleteImportAst(mod, (node) => node.specifiers.length === 0);
}
function getSimpleAstJson(ast) {
if (!ast) {
return ast;
} else if (Array.isArray(ast)) {
return ast.map(getSimpleAstJson);
} else if (typeof ast === "object") {
return Object.fromEntries(
Object.entries(ast).filter(([key]) => key !== "loc" && key !== "start" && key !== "end").map(([key, value]) => [key, getSimpleAstJson(value)])
);
} else {
return ast;
}
}