Last active
July 6, 2025 02:36
-
-
Save paralin/9de2163433cc82ec93951ffd304b3c87 to your computer and use it in GitHub Desktop.
analyze esbuild metadata.json
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
| // find-importers.js | |
| const fs = require('fs/promises'); | |
| /** | |
| * Finds and lists all modules that import any module whose path contains a specific substring. | |
| * | |
| * @param {string} metafilePath - Path to the esbuild meta.json file. | |
| * @param {string} targetSubstring - The substring to match in the imported paths (e.g., "/multiformats/dist"). | |
| */ | |
| async function findImporters(metafilePath, targetSubstring) { | |
| try { | |
| // 1. Validate inputs | |
| if (!metafilePath || !targetSubstring) { | |
| throw new Error('Both a metafile path and a target substring are required.'); | |
| } | |
| // 2. Read and parse the metafile | |
| console.log(`🔍 Reading metadata from: ${metafilePath}`); | |
| const metafileContent = await fs.readFile(metafilePath, 'utf-8'); | |
| const meta = JSON.parse(metafileContent); | |
| if (!meta.inputs) { | |
| throw new Error('Metafile does not contain a valid "inputs" key.'); | |
| } | |
| // A Set is used to automatically handle duplicate entries | |
| const importers = new Set(); | |
| const matchedImports = new Set(); | |
| // 3. Iterate through all modules in the bundle | |
| for (const importerPath in meta.inputs) { | |
| const moduleDetails = meta.inputs[importerPath]; | |
| // Check if this module has any imports | |
| if (moduleDetails.imports) { | |
| // Check each import of the current module | |
| for (const imp of moduleDetails.imports) { | |
| // If the imported path contains the target substring... | |
| if (imp.path.includes(targetSubstring)) { | |
| // ...then the current module is an "importer" we care about. | |
| importers.add(importerPath); | |
| matchedImports.add(imp.path) | |
| } | |
| } | |
| } | |
| } | |
| // 4. Report the results | |
| if (importers.size === 0) { | |
| console.log(`\n✅ No modules were found importing anything from "${targetSubstring}".`); | |
| } else { | |
| // Convert Set to Array and sort for consistent, readable output | |
| const sortedImporters = Array.from(importers).sort(); | |
| console.log(`\nFound ${matchedImports.size} module(s) matching *"${targetSubstring}"*`); | |
| console.log(`--------------------------------------------------`); | |
| console.log(`These are imported by the following ${sortedImporters.length} module(s):`); | |
| sortedImporters.forEach(importer => { | |
| console.log(` -> ${importer}`); | |
| }); | |
| } | |
| } catch (error) { | |
| console.error(`\n❌ ERROR: ${error.message}`); | |
| process.exit(1); | |
| } | |
| } | |
| // --- Main execution block --- | |
| const args = process.argv.slice(2); | |
| if (args.length !== 2) { | |
| console.log('Usage: node find-importers.js <path/to/meta.json> <substring-to-find>'); | |
| console.log('Example: node find-importers.js ./meta.json "/multiformats/dist"'); | |
| process.exit(1); | |
| } | |
| const [metafilePath, targetSubstring] = args; | |
| findImporters(metafilePath, targetSubstring); |
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
| // generate-graph.js | |
| const fs = require('fs/promises'); | |
| const path = require('path'); | |
| /** | |
| * Generates an HTML file with a dependency graph from an esbuild metafile. | |
| * @param {string} metafilePath - Path to the esbuild meta.json file. | |
| * @param {string} outputHtmlPath - Path for the output HTML file. | |
| */ | |
| async function createGraph(metafilePath, outputHtmlPath) { | |
| try { | |
| // 1. Read and parse the metafile | |
| console.log(`Reading metafile: ${metafilePath}`); | |
| const metafileContent = await fs.readFile(metafilePath, 'utf-8'); | |
| const meta = JSON.parse(metafileContent); | |
| if (!meta.inputs) { | |
| throw new Error('Metafile does not contain an "inputs" key.'); | |
| } | |
| const nodes = []; | |
| const edges = []; | |
| const nodeIds = new Set(); | |
| // 2. Process inputs to create nodes and edges | |
| for (const filePath in meta.inputs) { | |
| const details = meta.inputs[filePath]; | |
| const nodeId = filePath; | |
| // Add a node for the file if it doesn't exist | |
| if (!nodeIds.has(nodeId)) { | |
| nodes.push({ | |
| id: nodeId, | |
| label: path.basename(nodeId), // Use the filename as the label | |
| title: `${nodeId}\n${details.bytes} bytes`, // Tooltip | |
| value: details.bytes, // Use bytes for node sizing | |
| }); | |
| nodeIds.add(nodeId); | |
| } | |
| // Add edges for its imports | |
| if (details.imports) { | |
| for (const imp of details.imports) { | |
| edges.push({ | |
| from: nodeId, // The file doing the importing | |
| to: imp.path, // The file being imported | |
| arrows: 'to', // Draw a directed arrow | |
| }); | |
| } | |
| } | |
| } | |
| console.log(`Found ${nodes.length} files and ${edges.length} imports.`); | |
| // 3. Generate the HTML content | |
| const htmlContent = generateHtml(nodes, edges); | |
| // 4. Write the output file | |
| await fs.writeFile(outputHtmlPath, htmlContent); | |
| console.log(`✅ Success! Graph saved to ${outputHtmlPath}`); | |
| } catch (error) { | |
| console.error(`❌ Error: ${error.message}`); | |
| process.exit(1); | |
| } | |
| } | |
| /** | |
| * Creates the full HTML page content. | |
| * @param {Array} nodes - Array of node objects for vis-network. | |
| * @param {Array} edges - Array of edge objects for vis-network. | |
| * @returns {string} - A complete HTML string. | |
| */ | |
| function generateHtml(nodes, edges) { | |
| return ` | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ESBuild Dependency Graph</title> | |
| <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script> | |
| <style type="text/css"> | |
| html, body { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; | |
| width: 100%; | |
| height: 100%; | |
| margin: 0; | |
| padding: 0; | |
| overflow: hidden; | |
| background-color: #f7f7f7; | |
| } | |
| #graph-container { | |
| width: 100%; | |
| height: 100%; | |
| border: 1px solid lightgray; | |
| } | |
| .info { | |
| position: absolute; | |
| top: 10px; | |
| left: 10px; | |
| padding: 10px 15px; | |
| background: rgba(255, 255, 255, 0.9); | |
| border-radius: 8px; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.15); | |
| z-index: 10; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="info"> | |
| <strong>ESBuild Dependency Graph</strong><br> | |
| Nodes represent files, and edges represent imports. | |
| </div> | |
| <div id="graph-container"></div> | |
| <script type="text/javascript"> | |
| // 1. Create data sets | |
| const nodes = new vis.DataSet(${JSON.stringify(nodes, null, 2)}); | |
| const edges = new vis.DataSet(${JSON.stringify(edges, null, 2)}); | |
| // 2. Specify the container | |
| const container = document.getElementById('graph-container'); | |
| // 3. Provide the data | |
| const data = { | |
| nodes: nodes, | |
| edges: edges, | |
| }; | |
| // 4. Set the options | |
| const options = { | |
| nodes: { | |
| shape: 'box', | |
| margin: 10, | |
| font: { | |
| size: 14, | |
| color: '#333' | |
| }, | |
| borderWidth: 1, | |
| color: { | |
| border: '#cccccc', | |
| background: '#ffffff', | |
| highlight: { | |
| border: '#007bff', | |
| background: '#e6f2ff' | |
| } | |
| }, | |
| // Node size scales with file size (bytes) | |
| scaling: { | |
| min: 20, | |
| max: 100, | |
| label: { | |
| enabled: true, | |
| min: 14, | |
| max: 30 | |
| } | |
| } | |
| }, | |
| edges: { | |
| width: 1, | |
| color: { | |
| color: '#999', | |
| highlight: '#007bff' | |
| }, | |
| smooth: { | |
| type: 'cubicBezier', | |
| forceDirection: 'vertical', | |
| roundness: 0.4 | |
| } | |
| }, | |
| layout: { | |
| // A hierarchical layout is perfect for a DAG | |
| hierarchical: { | |
| direction: 'UD', // Up-Down | |
| sortMethod: 'directed', // Tries to point edges downwards | |
| shakeTowards: 'roots', | |
| }, | |
| }, | |
| interaction: { | |
| dragNodes: true, | |
| zoomView: true, | |
| dragView: true, | |
| tooltipDelay: 200, | |
| }, | |
| physics: { | |
| // Disable physics for a static hierarchical layout | |
| enabled: false, | |
| }, | |
| }; | |
| // 5. Initialize the network | |
| const network = new vis.Network(container, data, options); | |
| </script> | |
| </body> | |
| </html> | |
| `; | |
| } | |
| // --- Main execution --- | |
| const args = process.argv.slice(2); | |
| if (args.length !== 1) { | |
| console.log('Usage: node generate-graph.js <path/to/metafile.json>'); | |
| process.exit(1); | |
| } | |
| const metafilePath = args[0]; | |
| const outputHtmlPath = 'graph.html'; | |
| createGraph(metafilePath, outputHtmlPath); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment