UNPKG

mp-lens

Version:

微信小程序分析工具 (Unused Code, Dependencies, Visualization)

115 lines 5.39 kB
"use strict"; 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