axy/sourcemap
PHP library to create, load, search, and modify Source Map files. Supports renaming/removing sources, adjusting mappings, handling insert/remove blocks, concatenating maps for concatenated outputs, and merging intermediate maps. Works with maps only, not source/output code.
Installation:
composer require axy/sourcemap
Requires PHP 8.1+.
First Use Case: Load an existing source map and inspect its contents:
use axy\sourcemap\SourceMap;
$map = SourceMap::loadFromFile('path/to/file.js.map');
echo $map->file; // Outputs the original file name
print_r($map->sources->getNames()); // Lists source files
Where to Look First:
Initialize:
$resultMap = new SourceMap();
$resultMap->file = 'out.js';
$result = [];
$lineOffset = 0;
Process Each File:
foreach ($files as $file) {
$content = file_get_contents($file);
$content = preg_replace('~//# sourceMappingURL.*$~s', '', $content);
$result[] = $content;
$map = SourceMap::loadFromFile($file . '.map');
$resultMap->concat($map, $lineOffset);
$lineOffset += substr_count($content, "\n") + 1;
}
Save Results:
file_put_contents('out.js', implode("\n", $result));
$resultMap->save('out.js.map');
Load Final Map:
$finalMap = SourceMap::loadFromFile('out.js.map');
Merge Intermediate Maps:
$finalMap->merge('ab.js.map');
$finalMap->merge('cd.js.map');
Save Merged Map:
$finalMap->save('out.js.map');
Service Provider:
// app/Providers/AppServiceProvider.php
public function boot()
{
$this->app->singleton(SourceMap::class, function () {
return new SourceMap();
});
}
Facade (Optional):
// app/Facades/SourceMapFacade.php
public static function load($path)
{
return app(SourceMap::class)->loadFromFile($path);
}
Artisan Command for Concatenation:
// app/Console/Commands/ConcatenateAssets.php
public function handle()
{
$map = new SourceMap();
$map->file = 'public/assets/concatenated.js';
// ... concatenation logic ...
$map->save('public/assets/concatenated.js.map');
}
Zero-Based Indexing: All line/column numbers and indexes (sources/names) are zero-based. Forgetting this causes off-by-one errors.
// Wrong: Assumes 1-based
$position = $map->getPosition(1, 1); // Fails (should be 0, 0)
Mutable Objects in concat()/merge():
Passing a SourceMap instance to concat() or merge() mutates the original object. Clone it first if reuse is needed:
$map1->concat(clone $map2, 10); // Safe
$map1->concat($map2, 10); // Unsafe (modifies $map2)
Source Content Handling:
sourcesContent is optional and may be null for external files.sourcesContent does not update mappings automatically.File Path Resolution:
The library does not resolve paths relative to sourceRoot. Use absolute paths or handle resolution manually.
Validate Mappings:
Use getPosition() to verify mappings after modifications:
$pos = $map->getPosition(5, 10);
if (!$pos) {
throw new \RuntimeException("Mapping missing at line 5, column 10");
}
Inspect Changes: Dump the raw data before/after operations:
echo $map->toArray(); // Debug output
Handle Edge Cases:
sources or names arrays.mappings strings (e.g., malformed VLQ).Custom Position Filters:
Extend PosMap to add domain-specific filters:
class CustomPosMap extends \axy\sourcemap\PosMap {
public ?string $customField;
}
Pre/Post-Processing:
Hook into save() to transform the map before writing:
$map->onSave(function ($data) {
$data['customField'] = 'value';
return $data;
});
Integration with Laravel Mix/Vite: Use the package to rebuild source maps after custom transformations:
// In a Laravel Mix extension
mix.extend('customTransform', function () {
$map = SourceMap::loadFromFile('input.js.map');
// Modify $map...
$map->save('output.js.map');
});
find() with filters instead of getAllPositions() for performance.SourceMap objects can be expensive. Reuse instances where possible.How can I help you explore Laravel packages today?