mp-lens
Version:
微信小程序分析工具 (Unused Code, Dependencies, Visualization)
115 lines • 5.39 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.findUnusedAssets = findUnusedAssets;
const fs = __importStar(require("fs"));
const glob = __importStar(require("glob"));
const modern_ahocorasick_1 = __importDefault(require("modern-ahocorasick"));
const path = __importStar(require("path"));
const analyzer_1 = require("../analyzer/analyzer");
const command_init_1 = require("./command-init");
/**
* 检测项目中未被使用的资源文件(如图片)。
*
* 检测方式:
* - 首先通过 analyzeProject 获取所有可达(reachable)的源文件节点。
* - 在小程序根目录下(miniappRoot)glob 查找所有资源文件(如 png/jpg/svg 等)。
* - 仅用资源文件名(不含路径)作为关键词,使用 Aho-Corasick 算法批量在所有可达文件内容中做字符串匹配。
* - 只要资源文件名在任一可达文件内容中出现,即视为"被引用"。否则视为"未被引用"。
*
* 局限性:
* - 仅基于文件名字符串匹配,无法检测路径级别的精确引用。
* - 无法检测动态拼接、变量引用、base64、网络资源等间接用法。
* - 可能存在误报(如同名但非资源用途的字符串)或漏报(如资源名被拼接、加密、压缩等)。
* - 仅检测源码可达文件,不含 node_modules、构建产物等。
*
* 适合用于大批量资源初步清理,结果建议人工复核。
*
* @param projectRoot 项目根目录(绝对路径)
* @returns 未被引用的资源文件绝对路径数组
*/
async function findUnusedAssets(projectRoot) {
// 1. 初始化上下文,获取 miniappRoot、fileTypes、exclude 等
const context = await (0, command_init_1.initializeCommandContext)({ project: projectRoot });
const { miniappRoot, fileTypes, exclude, appJsonPath, appJsonContent, essentialFilesList, includeAssets, } = context;
// 2. 组装 AnalyzerOptions,确保 fileTypes 有默认值
const defaultFileTypes = ['js', 'ts', 'wxml', 'wxss', 'json'];
const options = {
miniappRoot,
fileTypes: Array.isArray(fileTypes) && fileTypes.length > 0 ? fileTypes : defaultFileTypes,
excludePatterns: exclude,
appJsonPath,
appJsonContent,
essentialFiles: essentialFilesList,
includeAssets,
};
// 3. 分析项目,获取 reachable 文件节点
const { projectStructure, reachableNodeIds } = await (0, analyzer_1.analyzeProject)(projectRoot, options);
const reachableFiles = projectStructure.nodes
.filter((n) => { var _a; return n.type === 'Module' && ((_a = n.properties) === null || _a === void 0 ? void 0 : _a.absolutePath) && reachableNodeIds.has(n.id); })
.map((n) => n.properties.absolutePath)
.filter(fs.existsSync);
// 4. 获取所有资源文件
const assetGlobPattern = '**/*.{png,jpg,jpeg,gif,svg,webp}';
const assetFiles = glob.sync(assetGlobPattern, { cwd: miniappRoot, absolute: true });
if (assetFiles.length === 0)
return [];
const assetNames = assetFiles.map((f) => path.basename(f));
// 5. 构建 Aho-Corasick 自动机
const ac = new modern_ahocorasick_1.default(assetNames);
// 6. 读取所有 reachable 文件内容,并用自动机查找
const usedAssets = new Set();
for (const file of reachableFiles) {
let content;
try {
content = await fs.promises.readFile(file, 'utf8');
}
catch (_a) {
continue;
}
for (const [, foundArr] of ac.search(content)) {
for (const found of foundArr) {
usedAssets.add(found);
}
}
}
// 7. 输出未被引用的资源
const unusedAssets = assetFiles.filter((f) => !usedAssets.has(path.basename(f)));
return unusedAssets;
}
//# sourceMappingURL=asset-usage-analyzer.js.map