Compare commits

...

14 Commits

Author SHA1 Message Date
Claude Bot
3282981fcf fix: update graph visualizer for Zig API changes
- Fix std.ArrayList API changes (initCapacity, append now require allocator)
- Fix ExprNodeList creation using fromOwnedSlice instead of .init()
- Add proper bounds checks for symbol lookups
- Improve error handling for directory creation
- Simplify buildSymbolChains logic

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-05 23:18:48 +00:00
Claude Bot
6c1466385f Merge main into graph-visualizer branch 2026-01-05 22:47:13 +00:00
Claude
c275a93bdb wip 2025-09-10 09:14:04 +02:00
Claude
3f97345088 Revert JSON serialization to use bun.JSON.toAST with BufferWriter
- Replace std.json.stringifyAlloc with bun.JSON.toAST approach
- Use BufferWriter for proper JSON printing in graph visualizer
- Maintains compatibility with Bun's JSON handling infrastructure

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 07:36:17 +02:00
Claude
2c76947aac Fix ASAN use-after-poison and enhance HTML visualizer
Fixed memory safety issue:
- ASAN detected use-after-poison when concatenating JavaScript output
- The compile_results_for_chunk memory was being accessed after deallocation
- Now make defensive copies of code immediately to avoid use-after-free
- Safely collect parts first, then concatenate if needed

Enhanced HTML visualizer to display new debugging data:
- Added panels for duplicate exports, symbol chains, and export details
- Show duplicate exports prominently with red warnings
- Display symbol resolution chains with color-coded link types
- Highlight ambiguous exports and conflicts
- Expanded symbol panel width to 400px for better visibility
- Added proper visualization of resolved exports

The debugger now provides complete visibility for:
- Duplicate export detection with file sources
- Symbol flow through import/export chains
- Ambiguous export warnings
- Full symbol resolution paths
- Export name to symbol mappings

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 06:30:31 +02:00
Claude
7186669efc Make graph visualizer a really good bundler debugger
Enhanced symbol tracking and debugging capabilities:
- Track actual export names with original symbol names
- Capture full symbol metadata including namespace aliases
- Add symbol chain tracking to follow resolution paths
- Track resolved exports with ambiguity detection
- Identify duplicate exports and conflicts
- Build complete symbol resolution chains
- Track re-exports and their targets
- Include all symbol flags for debugging

New data structures:
- SymbolChain: Tracks how symbols flow through imports/exports
- ChainLink: Individual steps in symbol resolution
- ResolvedExportInfo: Full export resolution data
- Enhanced ExportInfo with original names and locations

This provides comprehensive debugging for:
- Duplicate export issues
- Symbol resolution problems
- Import/export chain analysis
- Namespace merging conflicts
- Cross-file symbol tracking

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 06:21:26 +02:00
Claude
ed22a78c37 Remove all mocking and truncation from graph visualizer
- Remove generateMockSource() and generateMockOutput() functions
- Show "No source/output available" messages instead of fake data
- Remove 500/1000 char truncation limits - capture full source and output
- Concatenate all JavaScript compile results for complete output
- Default to 'after_generation' stage which has output code
- Only show real data from the bundler, no placeholders

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 05:35:01 +02:00
Claude
3aaa0e15f3 Complete working code flow visualizer with real output
The visualizer now fully works with:
- Actual source code snippets from input files (500 chars)
- Real JavaScript output from bundled chunks (1000 chars)
- New 'after_generation' stage that captures output after compilation
- HTML displays real source and output side-by-side
- Both visualizers generated: graph and code flow

To use:
1. Set BUN_BUNDLER_GRAPH_DUMP=all when bundling
2. Open code_flow_*.html from /tmp/bun-bundler-debug/
3. Load the after_generation JSON to see source→output transformation

Example:
  env BUN_BUNDLER_GRAPH_DUMP=all bun build app.js --target=browser

The code flow visualizer shows actual code transformations, while the
graph visualizer shows the overall bundle structure.

Tested and working with multi-file bundles showing real transformations.
2025-08-28 05:25:42 +02:00
Claude
6c5a063813 Add actual output code capture to graph visualizer
- Capture first 1000 chars of JavaScript output from compile_results_for_chunk
- Include source code snippets (first 500 chars) in FileData
- Add placeholders for source mappings extraction
- Both visualizers now generated: graph_visualizer.html and code_flow_visualizer.html

The code flow visualizer now has access to:
- Actual source code snippets from input files
- Real output code from bundled chunks
- Foundation for connecting symbols between source and output

Next steps:
- Fix JSON generation issues if any
- Parse source mappings to connect exact symbol positions
- Draw visual arrows between transformed symbols
- Show side-by-side diffs of transformations
2025-08-28 05:09:15 +02:00
Claude
55820bec90 Add code flow visualizer for source-to-output debugging
This new visualizer provides what's actually needed for debugging:
- Split-pane view showing source code and output code side-by-side
- Source snippets included in JSON dumps (first 500 chars)
- Placeholders for output snippets and source mappings
- Stage comparison to see what changes between bundler phases
- CodeMirror editors for syntax-highlighted code viewing
- Symbol flow tracking between source and output
- Foundation for overlaying transformations on actual code

The original graph visualizer is still generated for high-level analysis,
while the new code_flow visualizer focuses on the actual code transformations.

Next steps would be:
- Capture actual output code from compile results
- Extract and include source mappings
- Show real symbol transformations with arrows
- Highlight exact symbol locations in code
2025-08-28 04:49:19 +02:00
Claude
5b8e1b61dd Fix HTML visualizer bugs
- Fix symbols list: use source.symbols instead of source.samples
- Fix duplicate event listener memory leak by removing old listeners before adding new ones
- Implement cross-chunk imports edge visualization
- Preserve search field values when replacing elements
2025-08-28 04:09:18 +02:00
Claude
53f6a137aa Enhance graph visualizer with comprehensive data collection
- Add cross_chunk_imports details (not just count)
- Include import records and declared symbols for each part
- Add symbol linking information with use counts and flags
- Include runtime metadata (file counts, css/html detection)
- Fix missing dagre library in HTML visualizer
- Fix field name mismatches between JSON and HTML (snake_case)
- Add chunk metadata (unique_key, final_path, content_type)

This provides much more detailed information for debugging bundler issues,
especially for tracking duplicate exports and understanding symbol resolution.
2025-08-28 04:05:48 +02:00
Claude
e2ef1692f9 Fix HTML visualizer field names to match actual JSON structure
- Changed camelCase to snake_case to match JSON output
- Fixed field names: total_files, reachable_files, imports_and_exports, etc.
- Fixed accessing chunks properties: is_entry_point, files_in_chunk
- Fixed symbols structure: by_source instead of bySource
- Fixed imports target_source field name

All field accesses now correctly match the actual JSON structure produced.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 03:45:41 +02:00
Claude
bc32ddfbd3 Add comprehensive bundler graph visualizer for debugging
- Implements GraphVisualizer that dumps complete LinkerContext state to JSON
- Captures files, symbols, imports/exports, chunks, parts, and dependency graph
- Controlled via BUN_BUNDLER_GRAPH_DUMP environment variable (all/scan/chunks/compute/link)
- Uses proper bun.json.toAST and js_printer.printJSON for correct JSON serialization
- Enhanced json.toAST to support custom toExprForJSON methods and BabyList-like types
- Includes interactive D3.js HTML visualizer with multiple views
- Helps debug duplicate exports, circular dependencies, and bundling issues
- Outputs to /tmp/bun-bundler-debug/ with timestamped files

Usage: BUN_BUNDLER_GRAPH_DUMP=all bun build file.js

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 03:39:16 +02:00
7 changed files with 2911 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ pub const LinkerContext = struct {
pub const OutputFileListBuilder = @import("./linker_context/OutputFileListBuilder.zig");
pub const StaticRouteVisitor = @import("./linker_context/StaticRouteVisitor.zig");
pub const GraphVisualizer = @import("./graph_visualizer.zig").GraphVisualizer;
parse_graph: *Graph = undefined,
graph: LinkerGraph = undefined,
@@ -401,6 +402,13 @@ pub const LinkerContext = struct {
}
try this.scanImportsAndExports();
// Dump graph state after scan
if (comptime Environment.isDebug) {
GraphVisualizer.dumpGraphState(this, "after_scan", null) catch |err| {
debug("Failed to dump graph after scan: {}", .{err});
};
}
// Stop now if there were errors
if (this.log.hasErrors()) {
@@ -418,6 +426,13 @@ pub const LinkerContext = struct {
}
const chunks = try this.computeChunks(bundle.unique_key);
// Dump graph state after computing chunks
if (comptime Environment.isDebug) {
GraphVisualizer.dumpGraphState(this, "after_chunks", chunks) catch |err| {
debug("Failed to dump graph after chunks: {}", .{err});
};
}
if (this.log.hasErrors()) {
return error.BuildFailed;
@@ -428,12 +443,26 @@ pub const LinkerContext = struct {
}
try this.computeCrossChunkDependencies(chunks);
// Dump graph state after computing dependencies
if (comptime Environment.isDebug) {
GraphVisualizer.dumpGraphState(this, "after_compute", chunks) catch |err| {
debug("Failed to dump graph after compute: {}", .{err});
};
}
if (comptime FeatureFlags.help_catch_memory_issues) {
this.checkForMemoryCorruption();
}
this.graph.symbols.followAll();
// Final dump after linking
if (comptime Environment.isDebug) {
GraphVisualizer.dumpGraphState(this, "after_link", chunks) catch |err| {
debug("Failed to dump graph after link: {}", .{err});
};
}
return chunks;
}

View File

@@ -0,0 +1,757 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bun Bundler Code Flow Visualizer</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/material-darker.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/javascript/javascript.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: #0d1117;
color: #c9d1d9;
height: 100vh;
display: flex;
flex-direction: column;
}
#header {
background: #161b22;
border-bottom: 1px solid #30363d;
padding: 12px 20px;
display: flex;
align-items: center;
gap: 20px;
}
#header h1 {
font-size: 18px;
font-weight: 600;
}
#controls {
display: flex;
gap: 10px;
margin-left: auto;
}
#controls button, #controls select {
background: #21262d;
color: #c9d1d9;
border: 1px solid #30363d;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
#controls button:hover, #controls select:hover {
background: #30363d;
border-color: #8b949e;
}
#stage-selector {
display: flex;
gap: 10px;
align-items: center;
}
#stage-selector label {
font-size: 14px;
color: #8b949e;
}
#main-container {
flex: 1;
display: flex;
overflow: hidden;
position: relative;
}
.code-pane {
flex: 1;
display: flex;
flex-direction: column;
border-right: 1px solid #30363d;
}
.code-pane:last-child {
border-right: none;
}
.pane-header {
background: #161b22;
padding: 10px 15px;
border-bottom: 1px solid #30363d;
display: flex;
align-items: center;
gap: 10px;
}
.pane-title {
font-weight: 600;
font-size: 14px;
}
.file-selector {
margin-left: auto;
background: #21262d;
color: #c9d1d9;
border: 1px solid #30363d;
padding: 4px 8px;
border-radius: 4px;
font-size: 13px;
}
.code-container {
flex: 1;
position: relative;
overflow: hidden;
}
.CodeMirror {
height: 100%;
font-size: 13px;
font-family: 'Consolas', 'Monaco', monospace;
}
/* Symbol highlights */
.symbol-highlight {
position: relative;
background: rgba(139, 148, 158, 0.15);
border-bottom: 2px solid #58a6ff;
cursor: pointer;
}
.symbol-renamed {
background: rgba(251, 143, 68, 0.15);
border-bottom: 2px solid #fb8f44;
}
.symbol-removed {
background: rgba(248, 81, 73, 0.15);
border-bottom: 2px solid #f85149;
text-decoration: line-through;
}
.symbol-added {
background: rgba(63, 185, 80, 0.15);
border-bottom: 2px solid #3fb950;
}
/* Flow arrows overlay */
#flow-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1000;
}
.flow-line {
stroke: #58a6ff;
stroke-width: 2;
fill: none;
opacity: 0.6;
}
.flow-line.import {
stroke: #a371f7;
}
.flow-line.export {
stroke: #3fb950;
}
.flow-line.renamed {
stroke: #fb8f44;
}
.flow-arrow {
fill: #58a6ff;
}
/* Symbol info panel */
#symbol-panel {
position: absolute;
right: 20px;
top: 20px;
width: 400px;
max-height: 600px;
background: #161b22;
border: 1px solid #30363d;
border-radius: 6px;
padding: 15px;
display: none;
z-index: 1001;
overflow-y: auto;
}
#symbol-panel h3 {
font-size: 14px;
margin-bottom: 10px;
color: #f0f6fc;
}
.symbol-info {
font-size: 13px;
line-height: 1.6;
}
.symbol-info-row {
display: flex;
margin: 5px 0;
}
.symbol-info-label {
color: #8b949e;
min-width: 80px;
}
.symbol-info-value {
color: #c9d1d9;
font-family: 'Consolas', 'Monaco', monospace;
}
/* Stage diff panel */
#diff-panel {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 200px;
background: #0d1117;
border-top: 1px solid #30363d;
display: none;
overflow-y: auto;
}
.diff-header {
background: #161b22;
padding: 10px 15px;
border-bottom: 1px solid #30363d;
font-size: 14px;
font-weight: 600;
}
.diff-content {
padding: 15px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
}
.diff-item {
margin: 10px 0;
padding: 8px;
background: #161b22;
border-radius: 4px;
}
.diff-added {
border-left: 3px solid #3fb950;
}
.diff-removed {
border-left: 3px solid #f85149;
}
.diff-modified {
border-left: 3px solid #fb8f44;
}
/* Loading state */
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 18px;
color: #8b949e;
}
/* Split handle */
.split-handle {
position: absolute;
width: 3px;
height: 100%;
background: #30363d;
cursor: col-resize;
z-index: 999;
}
.split-handle:hover {
background: #58a6ff;
}
</style>
</head>
<body>
<div id="header">
<h1>🔍 Code Flow Visualizer</h1>
<div id="stage-selector">
<label>Stage:</label>
<select id="stage-select">
<option value="after_scan">After Scan</option>
<option value="after_compute">After Compute</option>
<option value="after_chunks">After Chunks</option>
<option value="after_link">After Link</option>
<option value="after_generation">After Generation (with output)</option>
</select>
<button id="compare-btn">📊 Compare Stages</button>
</div>
<div id="controls">
<input type="file" id="file-input" accept=".json" multiple style="display: none;">
<button onclick="document.getElementById('file-input').click()">📁 Load Dumps</button>
<button id="show-symbols">🔤 Symbols</button>
<button id="show-imports">📥 Imports</button>
<button id="show-exports">📤 Exports</button>
<button id="show-renames">✏️ Renames</button>
</div>
</div>
<div id="main-container">
<div id="loading">Loading visualizer...</div>
<div class="code-pane" id="source-pane" style="display: none;">
<div class="pane-header">
<span class="pane-title">📄 Source Code</span>
<select class="file-selector" id="source-file-select"></select>
</div>
<div class="code-container">
<textarea id="source-editor"></textarea>
</div>
</div>
<div class="split-handle" style="display: none;"></div>
<div class="code-pane" id="output-pane" style="display: none;">
<div class="pane-header">
<span class="pane-title">📦 Output Code</span>
<select class="file-selector" id="output-file-select"></select>
</div>
<div class="code-container">
<textarea id="output-editor"></textarea>
</div>
</div>
<svg id="flow-overlay"></svg>
<div id="symbol-panel">
<h3>Symbol Information</h3>
<div class="symbol-info"></div>
<div id="duplicate-exports" style="margin-top: 20px;"></div>
<div id="symbol-chains" style="margin-top: 20px;"></div>
<div id="export-details" style="margin-top: 20px;"></div>
</div>
<div id="diff-panel">
<div class="diff-header">Stage Differences</div>
<div class="diff-content"></div>
</div>
</div>
<script>
let sourceEditor = null;
let outputEditor = null;
let graphData = {};
let currentStage = 'after_generation'; // Default to stage with output
let currentSourceFile = 0;
let currentOutputFile = 0;
let symbolMappings = [];
let showSymbols = true;
let showImports = true;
let showExports = true;
let showRenames = true;
// Initialize CodeMirror editors
function initEditors() {
sourceEditor = CodeMirror.fromTextArea(document.getElementById('source-editor'), {
mode: 'javascript',
theme: 'material-darker',
lineNumbers: true,
readOnly: true,
lineWrapping: false
});
outputEditor = CodeMirror.fromTextArea(document.getElementById('output-editor'), {
mode: 'javascript',
theme: 'material-darker',
lineNumbers: true,
readOnly: true,
lineWrapping: false
});
// Show panes
document.getElementById('loading').style.display = 'none';
document.getElementById('source-pane').style.display = 'flex';
document.getElementById('output-pane').style.display = 'flex';
document.querySelector('.split-handle').style.display = 'block';
}
// Load graph data from files
document.getElementById('file-input').addEventListener('change', async (event) => {
const files = Array.from(event.target.files);
for (const file of files) {
const text = await file.text();
const data = JSON.parse(text);
const stage = data.stage;
graphData[stage] = data;
}
updateUI();
});
// Update UI with loaded data
function updateUI() {
const data = graphData[currentStage];
if (!data) return;
// Update file selectors
updateFileSelectors(data);
// Load source and output code
loadSourceCode(data);
loadOutputCode(data);
// Analyze and visualize symbol flow
analyzeSymbolFlow(data);
visualizeFlow();
}
function updateFileSelectors(data) {
const sourceSelect = document.getElementById('source-file-select');
const outputSelect = document.getElementById('output-file-select');
sourceSelect.innerHTML = '';
outputSelect.innerHTML = '';
// Add source files
(data.files || []).forEach((file, idx) => {
const option = document.createElement('option');
option.value = idx;
option.textContent = file.path || `File ${idx}`;
sourceSelect.appendChild(option);
});
// Add output chunks
(data.chunks || []).forEach((chunk, idx) => {
const option = document.createElement('option');
option.value = idx;
option.textContent = `Chunk ${idx} (${chunk.final_path || 'unnamed'})`;
outputSelect.appendChild(option);
});
}
function loadSourceCode(data) {
const file = data.files?.[currentSourceFile];
if (!file) {
sourceEditor.setValue('// No source code available');
return;
}
// Use actual source snippet if available
let sourceCode = '';
if (file.source_snippet) {
sourceCode = `// File: ${file.path}\n// Loader: ${file.loader}\n\n${file.source_snippet}`;
} else {
sourceCode = `// File: ${file.path}\n// No source code available\n\n// To see source code, ensure BUN_BUNDLER_GRAPH_DUMP=1 is set\n// and the bundler has captured source snippets.`;
}
sourceEditor.setValue(sourceCode);
// Highlight symbols
highlightSourceSymbols(data, file);
}
function loadOutputCode(data) {
const chunk = data.chunks?.[currentOutputFile];
if (!chunk) {
outputEditor.setValue('// No output code available');
return;
}
// Use actual output snippet if available
let outputCode = '';
if (chunk.output_snippet) {
outputCode = `// Chunk ${chunk.index}\n`;
outputCode += `// Entry point: ${chunk.is_entry_point}\n`;
outputCode += `// Output path: ${chunk.final_path || 'unknown'}\n\n`;
outputCode += chunk.output_snippet;
} else {
outputCode = `// Chunk ${chunk.index}\n// No output code available\n\n// Output code is only available in the 'after_generation' stage\n// after the bundler has completed code generation.`;
}
outputEditor.setValue(outputCode);
// Highlight transformed symbols
highlightOutputSymbols(data, chunk);
}
function highlightSourceSymbols(data, file) {
// This would mark symbols in the source code
// For real implementation, we'd use CodeMirror's markText
}
function highlightOutputSymbols(data, chunk) {
// This would mark transformed symbols in output
}
function analyzeSymbolFlow(data) {
symbolMappings = [];
// Clear panels
document.getElementById('duplicate-exports').innerHTML = '';
document.getElementById('symbol-chains').innerHTML = '';
document.getElementById('export-details').innerHTML = '';
// Analyze symbol chains for duplicates
if (data.symbol_chains && data.symbol_chains.length > 0) {
const exportNames = {};
data.symbol_chains.forEach(chain => {
if (!exportNames[chain.export_name]) {
exportNames[chain.export_name] = [];
}
exportNames[chain.export_name].push({
file: chain.source_file,
hasConflicts: chain.has_conflicts
});
});
// Show duplicate exports prominently
const duplicates = Object.entries(exportNames).filter(([_, sources]) => sources.length > 1);
if (duplicates.length > 0) {
const dupPanel = document.getElementById('duplicate-exports');
dupPanel.innerHTML = '<h4 style="color: red;">⚠️ DUPLICATE EXPORTS DETECTED</h4>';
duplicates.forEach(([name, sources]) => {
dupPanel.innerHTML += `
<div style="background: rgba(255,0,0,0.1); padding: 8px; margin: 5px 0; border-left: 3px solid red;">
<strong>"${name}"</strong> exported from files: ${sources.map(s => s.file).join(', ')}
</div>
`;
});
}
// Show symbol chains
const chainsPanel = document.getElementById('symbol-chains');
if (data.symbol_chains.length > 0) {
chainsPanel.innerHTML = '<h4>Symbol Resolution Chains</h4>';
const chainList = document.createElement('div');
chainList.style.cssText = 'max-height: 250px; overflow-y: auto; font-size: 12px;';
data.symbol_chains.slice(0, 30).forEach(chain => {
const isDuplicate = exportNames[chain.export_name]?.length > 1;
const chainDiv = document.createElement('div');
chainDiv.style.cssText = `
margin: 8px 0;
padding: 6px;
border-left: 3px solid ${chain.has_conflicts || isDuplicate ? 'orange' : '#4a5568'};
background: rgba(255,255,255,0.02);
`;
let html = `<strong style="${isDuplicate ? 'color: orange;' : ''}">${chain.export_name}</strong> (file ${chain.source_file})`;
if (chain.chain && chain.chain.length > 0) {
chain.chain.forEach(link => {
const color = link.link_type === 're-export' ? '#9f7aea' :
link.link_type === 'import' ? '#4299e1' :
'#48bb78';
html += `<br> → <span style="color: ${color};">${link.link_type}</span>: ${link.symbol_name} @ file ${link.file_index}`;
});
}
if (chain.has_conflicts) {
html += `<br><span style="color: orange;">⚠️ Has conflicts with ${chain.conflict_sources?.length || 0} sources</span>`;
}
chainDiv.innerHTML = html;
chainList.appendChild(chainDiv);
});
chainsPanel.appendChild(chainList);
}
}
// Show resolved exports with details
if (data.imports_and_exports?.resolved_exports) {
const resolved = data.imports_and_exports.resolved_exports;
const ambiguous = resolved.filter(e => e.potentially_ambiguous);
if (ambiguous.length > 0) {
const exportPanel = document.getElementById('export-details');
exportPanel.innerHTML = '<h4 style="color: orange;">Ambiguous Exports</h4>';
const ambList = document.createElement('div');
ambList.style.cssText = 'max-height: 150px; overflow-y: auto; font-size: 12px;';
ambiguous.forEach(exp => {
ambList.innerHTML += `
<div style="background: rgba(255,165,0,0.1); padding: 6px; margin: 4px 0;">
<strong>${exp.export_alias}</strong> (file ${exp.source})<br>
Target: ${exp.target_source !== null ? `file ${exp.target_source}` : 'unresolved'}<br>
Ambiguous: ${exp.ambiguous_count} sources
</div>
`;
});
exportPanel.appendChild(ambList);
}
}
// Analyze regular symbol mappings
const symbols = data.symbols?.by_source || [];
symbols.forEach(source => {
source.symbols?.forEach(symbol => {
if (symbol.link) {
symbolMappings.push({
source: symbol,
sourceFile: source.source_index,
transformed: symbol.link,
type: symbol.kind
});
}
});
});
}
function visualizeFlow() {
const svg = d3.select('#flow-overlay');
svg.selectAll('*').remove();
// Draw flow arrows between source and output
// This would connect highlighted symbols
if (!showSymbols) return;
// For now, just show we're ready to draw
console.log('Ready to visualize', symbolMappings.length, 'symbol flows');
}
// Stage selector
document.getElementById('stage-select').addEventListener('change', (e) => {
currentStage = e.target.value;
updateUI();
});
// File selectors
document.getElementById('source-file-select').addEventListener('change', (e) => {
currentSourceFile = parseInt(e.target.value);
loadSourceCode(graphData[currentStage]);
});
document.getElementById('output-file-select').addEventListener('change', (e) => {
currentOutputFile = parseInt(e.target.value);
loadOutputCode(graphData[currentStage]);
});
// Toggle buttons
document.getElementById('show-symbols').addEventListener('click', () => {
showSymbols = !showSymbols;
document.getElementById('show-symbols').style.opacity = showSymbols ? '1' : '0.5';
visualizeFlow();
});
document.getElementById('show-imports').addEventListener('click', () => {
showImports = !showImports;
document.getElementById('show-imports').style.opacity = showImports ? '1' : '0.5';
visualizeFlow();
});
document.getElementById('show-exports').addEventListener('click', () => {
showExports = !showExports;
document.getElementById('show-exports').style.opacity = showExports ? '1' : '0.5';
visualizeFlow();
});
document.getElementById('show-renames').addEventListener('click', () => {
showRenames = !showRenames;
document.getElementById('show-renames').style.opacity = showRenames ? '1' : '0.5';
visualizeFlow();
});
// Compare stages
document.getElementById('compare-btn').addEventListener('click', () => {
const diffPanel = document.getElementById('diff-panel');
diffPanel.style.display = diffPanel.style.display === 'none' ? 'block' : 'none';
if (diffPanel.style.display === 'block') {
compareStages();
}
});
function compareStages() {
const stages = Object.keys(graphData).sort();
if (stages.length < 2) return;
const diffContent = document.querySelector('.diff-content');
diffContent.innerHTML = '';
for (let i = 1; i < stages.length; i++) {
const prev = graphData[stages[i-1]];
const curr = graphData[stages[i]];
const diff = document.createElement('div');
diff.className = 'diff-item';
diff.innerHTML = `
<strong>${stages[i-1]}${stages[i]}</strong><br>
Files: ${prev.metadata?.total_files}${curr.metadata?.total_files}<br>
Symbols: ${prev.symbols?.total_symbols}${curr.symbols?.total_symbols}<br>
Chunks: ${(prev.chunks?.length || 0)}${(curr.chunks?.length || 0)}
`;
if (curr.metadata?.total_files > prev.metadata?.total_files) {
diff.classList.add('diff-added');
} else if (curr.metadata?.total_files < prev.metadata?.total_files) {
diff.classList.add('diff-removed');
} else {
diff.classList.add('diff-modified');
}
diffContent.appendChild(diff);
}
}
// Initialize on load
window.addEventListener('load', () => {
initEditors();
});
// Handle split pane resizing
const splitHandle = document.querySelector('.split-handle');
let isResizing = false;
splitHandle.addEventListener('mousedown', (e) => {
isResizing = true;
document.body.style.cursor = 'col-resize';
});
document.addEventListener('mousemove', (e) => {
if (!isResizing) return;
const container = document.getElementById('main-container');
const x = e.clientX - container.offsetLeft;
const width = container.offsetWidth;
const percentage = (x / width) * 100;
document.getElementById('source-pane').style.flex = `0 0 ${percentage}%`;
document.getElementById('output-pane').style.flex = `0 0 ${100 - percentage}%`;
splitHandle.style.left = `${percentage}%`;
});
document.addEventListener('mouseup', () => {
isResizing = false;
document.body.style.cursor = '';
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -193,6 +193,8 @@ pub fn generateChunksInParallel(
}
}
// Note: Output capture moved to writeOutputFilesToDisk where it's safe to access
// When bake.DevServer is in use, we're going to take a different code path at the end.
// We want to extract the source code of each part instead of combining it into a single file.
// This is so that when hot-module updates happen, we can:

View File

@@ -259,6 +259,17 @@ pub fn writeOutputFilesToDisk(
break :brk null;
};
// Capture final output for debugging (safe here as we have the actual buffer)
if (comptime bun.Environment.isDebug) {
if (chunk_index_in_chunks_list == chunks.len - 1) {
// Only dump once at the end with all chunks' output
const GraphVisualizer = @import("../graph_visualizer.zig").GraphVisualizer;
GraphVisualizer.dumpGraphStateWithOutput(c, "after_write", chunks, code_result.buffer) catch |err| {
Output.warn("Failed to dump graph after write: {}", .{err});
};
}
}
switch (jsc.Node.fs.NodeFS.writeFileWithPathBuffer(
&pathbuf,
.{

View File

@@ -491,6 +491,16 @@ pub fn toAST(
) anyerror!js_ast.Expr {
const type_info: std.builtin.Type = @typeInfo(Type);
// Check if type has custom toExprForJSON method (only for structs, unions, and enums)
switch (type_info) {
.@"struct", .@"union", .@"enum" => {
if (comptime @hasDecl(Type, "toExprForJSON")) {
return try Type.toExprForJSON(&value, allocator);
}
},
else => {},
}
switch (type_info) {
.bool => {
return Expr{
@@ -538,7 +548,7 @@ pub fn toAST(
const exprs = try allocator.alloc(Expr, value.len);
for (exprs, 0..) |*ex, i| ex.* = try toAST(allocator, @TypeOf(value[i]), value[i]);
return Expr.init(js_ast.E.Array, js_ast.E.Array{ .items = exprs }, logger.Loc.Empty);
return Expr.init(js_ast.E.Array, js_ast.E.Array{ .items = js_ast.ExprNodeList.fromOwnedSlice(exprs) }, logger.Loc.Empty);
},
else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"),
},
@@ -550,9 +560,20 @@ pub fn toAST(
const exprs = try allocator.alloc(Expr, value.len);
for (exprs, 0..) |*ex, i| ex.* = try toAST(allocator, @TypeOf(value[i]), value[i]);
return Expr.init(js_ast.E.Array, js_ast.E.Array{ .items = exprs }, logger.Loc.Empty);
return Expr.init(js_ast.E.Array, js_ast.E.Array{ .items = .init(exprs) }, logger.Loc.Empty);
},
.@"struct" => |Struct| {
// Check if struct has a slice() method - treat it as an array
if (comptime @hasField(Type, "ptr") and @hasField(Type, "len")) {
// This looks like it might be array-like, check for slice method
if (comptime @hasDecl(Type, "slice")) {
const slice = value.slice();
const exprs = try allocator.alloc(Expr, slice.len);
for (exprs, 0..) |*ex, i| ex.* = try toAST(allocator, @TypeOf(slice[i]), slice[i]);
return Expr.init(js_ast.E.Array, js_ast.E.Array{ .items = js_ast.ExprNodeList.fromOwnedSlice(exprs) }, logger.Loc.Empty);
}
}
const fields: []const std.builtin.Type.StructField = Struct.fields;
var properties = try BabyList(js_ast.G.Property).initCapacity(allocator, fields.len);