symfony/asset-mapper
Symfony AssetMapper exposes asset directories and publishes them to a public folder with digested (versioned) filenames. It can also generate an importmap, letting you use modern JavaScript modules without a build step.
Install the Package
composer require symfony/asset-mapper
Note: Laravel does not natively support this package, so manual integration is required.
Configure Asset Mapping
Create a Symfony-style configuration in config/packages/asset_mapper.yaml (or adapt to Laravel’s config/app.php):
framework:
asset_mapper:
public_directory: '%kernel.project_dir%/public/build'
mappings:
js: ['%kernel.project_dir%/resources/js']
css: ['%kernel.project_dir%/resources/css']
For Laravel, use a service provider to register the mapper.
Run the Mapper Use Symfony’s CLI (or create a Laravel Artisan command):
php bin/console asset:map
This copies assets to public/build with hashed filenames (e.g., app.[hash].js).
Generate an Importmap
php bin/console importmap:dump
Outputs importmap.json to define ES module paths (e.g., "app.js": "/build/app.[hash].js").
Use in Blade/Templates Reference assets via hashed paths:
<script type="importmap">
{{ file_get_contents(public_path('build/importmap.json')) }}
</script>
<script src="{{ asset('build/app.[hash].js') }}" type="module"></script>
Replace manual filename hashing (e.g., styles.css?v=123) with auto-generated digests:
// Before: Manual hashing
<link href="/css/styles.css?v={{ filemtime(public_path('css/styles.css')) }}" rel="stylesheet">
// After: AssetMapper
<link href="{{ asset('build/styles.[hash].css') }}" rel="stylesheet">
Run php artisan asset:map to update hashes on deploy.
Enable import statements in vanilla JS:
// resources/js/app.js
import { createApp } from 'https://esm.sh/vue';
<!-- Blade template -->
<script type="importmap">
{{ file_get_contents(public_path('build/importmap.json')) }}
</script>
<script type="module" src="{{ asset('build/app.[hash].js') }}"></script>
Run php artisan importmap:dump to auto-generate the importmap.
Service Provider Setup
Create app/Providers/AssetMapperServiceProvider.php:
use Symfony\Component\AssetMapper\AssetMapper;
use Symfony\Component\AssetMapper\AssetMapperBundle\DependencyInjection\Configuration;
class AssetMapperServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(AssetMapper::class, function ($app) {
$config = new Configuration();
$mappedConfig = $config->getMappedConfiguration(
$app['config']['asset_mapper'] ?? []
);
return new AssetMapper($mappedConfig);
});
}
}
Artisan Command Wrapper
Extend Symfony’s commands in app/Console/Commands/MapAssets.php:
use Symfony\Component\AssetMapper\Command\MapAssetsCommand;
class MapAssets extends Command
{
protected $signature = 'asset:map';
protected $description = 'Map assets to public directory with hashed filenames';
public function handle()
{
$command = new MapAssetsCommand();
return $command->run(new Application(), ['command' => $this->getName()]);
}
}
Configuration in config/app.php
'asset_mapper' => [
'public_directory' => public_path('build'),
'mappings' => [
'js' => [resource_path('js')],
'css' => [resource_path('css')],
],
'importmap' => [
'output_path' => public_path('build/importmap.json'),
],
],
Start with Static Assets
Use asset:map for CSS/JS/images without importmap:
php artisan asset:map
Benefit: Cache-busted filenames with zero config changes.
Add Importmap Later Enable ES modules for new features:
php artisan importmap:dump
Benefit: Modern JS without a bundler.
Hybrid Approach Combine with Laravel Mix/Vite:
asset:map for third-party libraries (e.g., node_modules).Extend the mapper for runtime-generated assets (e.g., user uploads):
use Symfony\Component\AssetMapper\AssetMapperInterface;
class CustomAssetMapper implements AssetMapperInterface
{
public function map(string $path): string
{
if (str_starts_with($path, 'uploads/')) {
return 'build/uploads/' . pathinfo($path, PATHINFO_FILENAME) . '.' . hash('sha256', filemtime($path));
}
return $path; // Fallback to default mapper
}
}
Register as a binding in the service provider.
Define external modules (e.g., React from a CDN) in importmap.json:
{
"imports": {
"react": "https://esm.sh/react@18",
"react-dom": "https://esm.sh/react-dom@18"
}
}
Generated via php artisan importmap:dump --cdn.
Laravel-Symfony Architecture Mismatch
AssetMapper expects Symfony’s HttpKernel and FrameworkBundle.$kernel = $this->app->make(KernelInterface::class);
$mapper = new AssetMapper($config, $kernel->getContainer());
Caching Conflicts
config_cache.php) may override AssetMapper configurations.asset_mapper settings:
php artisan config:clear
php artisan cache:clear
Importmap JSON Injection
importmap.json may expose internal paths if not sanitized.AssetMapper\ImportMap\ImportMapGenerator with a whitelist:
$generator = new ImportMapGenerator();
$generator->addAllowedPath(resource_path('js'));
Concurrent Request Flooding
importmap:dump).--batch flag:
php artisan importmap:dump --batch=10
Blade Asset Helper Conflicts
asset() helper may not resolve hashed paths.if (!function_exists('asset_mapper')) {
function asset_mapper($path) {
return asset('build/' . pathinfo($path, PATHINFO_FILENAME) . '.' . hash('sha256', filemtime(public_path($path))));
}
}
Dry Run Mode Test changes without writing files:
php artisan asset:map --dry-run
php artisan importmap:dump --dry-run
Verbose Output Enable debug logs:
php artisan asset:map -v
Look for AssetMapper entries in storage/logs/laravel.log.
Manual Path Resolution Check mapped paths:
$mapper = app(AssetMapper::class);
dd($mapper->map(resource_path('js/app.js')));
Importmap Validation Validate JSON syntax:
php artisan importmap:dump && php -r "echo json_last_error();"
Custom Digesters Override filename hashing:
use Symfony\Component\AssetMapper\Digester\DigesterInterface;
class CustomDigester implements DigesterInterface
{
public function digest(string $content): string
{
return hash('crc32b', $content); // Faster but less unique
}
}
Register in the service provider.
Asset Filters
Exclude files (e.g., .min.js):
use Symfony\Component\AssetMapper\AssetMapperInterface;
How can I help you explore Laravel packages today?