spatie/sourcemaps-lookup
Fast, memory-efficient Source Map v3 lookup for PHP. Resolve JavaScript stack frames to original source file, line/column, symbol name, and enclosing scope. Optimized for the read path and high-volume error symbolicating from uploaded sourcemaps.
Installation:
composer require spatie/sourcemaps-lookup
First Use Case: Load a source map and resolve a stack trace position:
use Spatie\SourcemapsLookup\SourceMapLookup;
$map = SourceMapLookup::fromFile(public_path('js/bundle.js.map'));
$position = $map->lookup(42, 17); // Line 42, column 17 in the generated JS
if ($position) {
echo "Original: {$position->sourceFileName}:{$position->sourceLine}";
}
Where to Look First:
lookup, scopeAt, sourceContent).Load Maps Efficiently: Cache maps by URL to avoid redundant parsing:
$mapCache = [];
foreach ($stackFrames as $frame) {
$mapCache[$frame['sourceMapUrl']] ??= SourceMapLookup::fromJson($frame['sourceMap']);
$position = $mapCache[$frame['sourceMapUrl']]->lookup($frame['line'], $frame['column']);
}
Resolve Positions with Context:
Combine lookup with scopeAt for function names and sourceLines for code snippets:
$position = $map->lookup($line, $column);
$scope = $map->scopeAt($line, $column);
$snippet = $map->sourceLines(
$position->sourceFileIndex,
$position->sourceLine - 5,
$position->sourceLine + 5
);
Handle Edge Cases:
null from lookup().isIgnored() to filter vendor/framework frames:
if ($position && !$map->isIgnored($position->sourceFileName)) {
// Render user code
}
Service Provider Setup: Register a singleton for global map caching:
$this->app->singleton('sourcemap', function () {
return new SourceMapLookupCache();
});
Middleware for Error Pages: Attach resolved positions to exceptions:
public function handle($request, Throwable $exception)
{
$map = SourceMapLookup::fromFile(storage_path('app/public/js/bundle.js.map'));
$position = $map->lookup($exception->getLine(), $exception->getColumn());
$exception->setOriginalSource($position);
return parent::handle($request, $exception);
}
Blade Directives for Debugging:
Create a @sourcemap directive to render stack traces with original sources:
Blade::directive('sourcemap', function ($expression) {
return "<?php echo resolve('sourcemap')->render($expression); ?>";
});
Lazy Parsing Overhead:
lookup() on a line parses all preceding segments. For bulk lookups, pre-warm critical lines:
$map->lookup(1, 0); // Force-parse line 1 to avoid cold-start delays
Source Root Resolution:
sourceFileName is resolved with sourceRoot but not further resolved to absolute paths. Handle path joining manually if needed:
$absolutePath = rtrim($map->sourceRoot, '/') . '/' . $position->sourceFileName;
Scope Resolution Limitations:
scopeAt() is a heuristic polyfill. For accurate scopes, wait for bundler support for the ECMA-426 Scopes proposal.Memory Usage:
sourceLines() instead of sourceContent() for snippets to avoid loading entire files.Validate Source Maps:
Use try-catch to handle malformed maps:
try {
$map = SourceMapLookup::fromJson($json);
} catch (InvalidSourceMap $e) {
Log::error("Invalid source map: " . $e->getMessage());
return null;
}
Inspect Mappings: Log raw mappings for debugging:
$map = SourceMapLookup::fromArray($data);
Log::debug('Sources:', $map->sourceNames());
Performance Profiling:
memory_get_usage() to monitor spikes during bulk lookups.findGenerated() separately—it builds a full reverse index on first call.Custom Position Resolvers:
Extend Spatie\SourcemapsLookup\Position or create a decorator to add metadata (e.g., Git blame info):
class EnhancedPosition extends Position {
public function gitBlame(): ?string {
// Integrate with a Git service
}
}
Source Content Caching:
Cache sourcesContent in Laravel’s cache:
$content = Cache::remember(
"sourcemap:{$map->hash()}:{$position->sourceFileIndex}",
now()->addHours(1),
fn() => $map->sourceContent($position->sourceFileIndex)
);
Reverse Lookup Optimization:
For frequent findGenerated() calls, pre-compute the reverse index in a Laravel job:
ReverseIndex::dispatch($map->toArray())->onQueue('high');
How can I help you explore Laravel packages today?