UNPKG

mp-lens

Version:

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

285 lines 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.formatBytes = formatBytes; exports.buildTreeWithStats = buildTreeWithStats; /** * Helper function to format bytes into human-readable form. */ function formatBytes(bytes, decimals = 2) { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; // Handle cases where bytes might be unexpectedly small leading to i < 0 if (bytes < 1) return bytes.toFixed(dm) + ' Bytes'; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } /** * Calculates statistics from a collection of module IDs. */ function calculateStatsFromModuleIds(moduleIds, nodeMap) { const stats = { fileCount: 0, totalSize: 0, fileTypes: {}, sizeByType: {}, }; for (const moduleId of moduleIds) { const moduleNode = nodeMap.get(moduleId); if (moduleNode && moduleNode.type === 'Module' && moduleNode.properties) { const properties = moduleNode.properties; const fileSize = properties.fileSize || 0; const fileExt = properties.fileExt || 'unknown'; stats.fileCount++; stats.totalSize += fileSize; stats.fileTypes[fileExt] = (stats.fileTypes[fileExt] || 0) + 1; stats.sizeByType[fileExt] = (stats.sizeByType[fileExt] || 0) + fileSize; } } return stats; } /** * Ensures that every Module node has basic property information. * Used for leaf nodes in the dependency graph. */ function ensureModuleProperties(node) { // If it's not a Module, return original properties if (node.type !== 'Module') { return node.properties || {}; } // For Module nodes, ensure they have at least basic file information const properties = node.properties || {}; return { ...properties, // If fileCount is missing, default to 1 fileCount: properties.fileCount !== undefined ? properties.fileCount : 1, // If totalSize is missing but fileSize exists, use that totalSize: properties.totalSize !== undefined ? properties.totalSize : properties.fileSize !== undefined ? properties.fileSize : 0, // Ensure fileTypes exists fileTypes: properties.fileTypes || { [properties.fileExt || 'unknown']: 1, }, // Ensure sizeByType exists sizeByType: properties.sizeByType || { [properties.fileExt || 'unknown']: properties.fileSize || 0, }, }; } /** * Collects all unique reachable 'Module' node IDs starting from a given graph node ID. * Traverses through all link types. */ function collectAllReachableModulesFrom(startGraphNodeId, nodeMap, linksFromMap) { // if (collectionCache.has(startGraphNodeId)) { // return new Set(collectionCache.get(startGraphNodeId)!); // } const reachableModuleIds = new Set(); const queue = [startGraphNodeId]; const visitedInThisTraversal = new Set(); visitedInThisTraversal.add(startGraphNodeId); while (queue.length > 0) { const currentId = queue.shift(); const currentNode = nodeMap.get(currentId); if (currentNode && currentNode.type === 'Module') { reachableModuleIds.add(currentId); } const outgoingLinks = linksFromMap.get(currentId) || []; for (const link of outgoingLinks) { const targetId = link.target; if (!visitedInThisTraversal.has(targetId)) { visitedInThisTraversal.add(targetId); // Only add to queue if the target node exists to prevent errors if (nodeMap.has(targetId)) { queue.push(targetId); } } } } // collectionCache.set(startGraphNodeId, new Set(reachableModuleIds)); return reachableModuleIds; } /** * Finds 'Module' nodes that are direct structural parts of a non-Module node * (e.g., app.js for 'app', page.js for a Page). */ function findDirectStructuralModules(currentGraphNodeId, allLinks, nodeMap) { const directModules = []; for (const link of allLinks) { if (link.source === currentGraphNodeId) { const targetNode = nodeMap.get(link.target); // These links define the constituent files of an App, Page, Component, etc. if (targetNode && targetNode.type === 'Module' && (link.type === 'Structure' || link.type === 'Config')) { // 'Config' for app.json directModules.push(targetNode); } } } return directModules; } /** * Recursive helper to build the subtree and calculate stats. */ function processNodeRecursive(currentGraphNodeId, nodeMap, allLinks, // Needed for findDirectStructuralModules linksFromMap, // Needed for collectAllReachableModulesFrom // Map defining tree structure: parentId -> Set<childId (non-Module)> treeChildrenMap, visitedForTreeCycles) { const nodeData = nodeMap.get(currentGraphNodeId); if (!nodeData) { // console.warn(`[TreeProcessor] Node data not found for ID: ${currentGraphNodeId}`); return null; } if (visitedForTreeCycles.has(currentGraphNodeId)) { // console.warn(`[TreeProcessor] Cycle detected in tree structure involving node: ${currentGraphNodeId}`); return { treeNode: { id: nodeData.id, label: nodeData.label + ' (Cycle)', type: nodeData.type, properties: ensureModuleProperties(nodeData), children: [], }, reachableModuleIds: new Set(), // No modules from a cycle node itself }; } visitedForTreeCycles.add(currentGraphNodeId); const aggregatedModuleIds = new Set(); // 1. Collect modules from the current node's own direct/constituent files and their dependencies if (nodeData.type !== 'Module') { // App, Page, Package, Component const directModules = findDirectStructuralModules(currentGraphNodeId, allLinks, nodeMap); for (const moduleNode of directModules) { const modulesReachableFromThisFile = collectAllReachableModulesFrom(moduleNode.id, nodeMap, linksFromMap /* collectionCache */); modulesReachableFromThisFile.forEach((id) => aggregatedModuleIds.add(id)); } } else { // If the current node itself is a Module (e.g. if root is a module, or a module somehow becomes a tree node) // This case should be rare for typical tree structures but handled for completeness. const modulesReachableFromThisFile = collectAllReachableModulesFrom(currentGraphNodeId, nodeMap, linksFromMap /* collectionCache */); modulesReachableFromThisFile.forEach((id) => aggregatedModuleIds.add(id)); // For Module nodes, always include itself in the reachable set // This ensures it has its own stats even if it has no dependencies aggregatedModuleIds.add(currentGraphNodeId); } // 2. Process children (non-Module, 'Structure' links that define the tree hierarchy) const childrenTreeNodes = []; const childGraphNodeIds = treeChildrenMap.get(currentGraphNodeId) || new Set(); for (const childId of childGraphNodeIds) { // Ensure child is not a Module type for tree structure (already filtered by treeChildrenMap construction) const childNode = nodeMap.get(childId); if (childNode /* && childNode.type !== 'Module' -- this check should be implicit now */) { const childResult = processNodeRecursive(childId, nodeMap, allLinks, linksFromMap, treeChildrenMap, new Set(visitedForTreeCycles) /* collectionCache */); if (childResult) { childrenTreeNodes.push(childResult.treeNode); childResult.reachableModuleIds.forEach((id) => aggregatedModuleIds.add(id)); } } } // Sort children: Packages, Pages, Components, then alphabetically by label childrenTreeNodes.sort((a, b) => { var _a, _b; const typeOrder = { Package: 1, Page: 2, Component: 3 }; const orderA = (_a = typeOrder[a.type]) !== null && _a !== void 0 ? _a : 99; const orderB = (_b = typeOrder[b.type]) !== null && _b !== void 0 ? _b : 99; if (orderA !== orderB) return orderA - orderB; return a.label.localeCompare(b.label); }); // 3. Calculate stats for the current node based on all aggregated module IDs let finalProperties; if (nodeData.type === 'Module') { // For Module nodes, ensure they have their own properties with at least basic values finalProperties = ensureModuleProperties(nodeData); } else { // For non-Module nodes, calculate stats from all aggregated modules const currentStats = calculateStatsFromModuleIds(aggregatedModuleIds, nodeMap); finalProperties = { ...nodeData.properties, // Preserve original properties fileCount: currentStats.fileCount, totalSize: currentStats.totalSize, fileTypes: currentStats.fileTypes, sizeByType: currentStats.sizeByType, reachableModuleIds: aggregatedModuleIds, // Store the set of module IDs }; } const treeNode = { id: nodeData.id, label: nodeData.label, type: nodeData.type, properties: finalProperties, children: childrenTreeNodes.length > 0 ? childrenTreeNodes : undefined, }; return { treeNode, reachableModuleIds: aggregatedModuleIds }; } /** * Converts flat graph data (nodes and links) into a hierarchical tree structure * with calculated statistics for each node. */ function buildTreeWithStats(projectStructure) { var _a; const { nodes, links, rootNodeId: projectRootId } = projectStructure; if (!nodes || nodes.length === 0) { return { id: 'empty', label: 'No Data Available', type: 'Unknown' }; } const nodeMap = new Map(); nodes.forEach((node) => nodeMap.set(node.id, node)); // Ensure all Module nodes have proper property information nodes.forEach((node) => { if (node.type === 'Module') { node.properties = ensureModuleProperties(node); } }); const linksFromMap = new Map(); links.forEach((link) => { if (!linksFromMap.has(link.source)) { linksFromMap.set(link.source, []); } linksFromMap.get(link.source).push(link); }); // Build treeChildrenMap: parentId -> Set<childId (non-Module)> // These are 'Structure' links that form the primary tree hierarchy. const treeChildrenMap = new Map(); links.forEach((link) => { if (link.type === 'Structure') { const targetNode = nodeMap.get(link.target); // Only include non-Module children in the tree view for a cleaner hierarchy. // Modules are handled via stats aggregation. if (targetNode && targetNode.type !== 'Module') { if (!treeChildrenMap.has(link.source)) { treeChildrenMap.set(link.source, new Set()); } // Avoid adding self as child in the tree structure if (link.source !== link.target) { treeChildrenMap.get(link.source).add(link.target); } } } }); let rootId = projectRootId; if (!rootId || !nodeMap.has(rootId)) { // console.warn('[TreeProcessor] projectStructure.rootNodeId not found or invalid, trying 'app'...'); rootId = 'app'; // Common fallback for miniapps } if (!nodeMap.has(rootId)) { // console.warn(`[TreeProcessor] 'app' node not found, trying first node in the list: ${nodes[0]?.id}`); rootId = (_a = nodes[0]) === null || _a === void 0 ? void 0 : _a.id; } if (!rootId || !nodeMap.has(rootId)) { // console.error('[TreeProcessor] Could not determine a valid root node for the tree.'); return { id: 'error_root', label: 'Could not build tree: No valid root', type: 'Unknown' }; } // const collectionCache = new Map<string, Set<string>>(); // Instantiate cache for collectAllReachableModulesFrom const result = processNodeRecursive(rootId, nodeMap, links, linksFromMap, treeChildrenMap, new Set() /* collectionCache */); return result ? result.treeNode : null; } //# sourceMappingURL=dependency-tree-processor.js.map