jeroenvdheuve/assetic-cache-busting-worker-bundle
Symfony bundle that adds an Assetic worker appending cache-busting query strings to asset URLs, so updated CSS, JS, and images are served fresh without stale browser caches. Integrates with Assetic's worker pipeline for automatic asset versioning.
Read this first: this is a Symfony bundle, not a Laravel package. It registers a worker into Symfony's AsseticBundle asset pipeline so that dumped asset filenames get a content hash (e.g. app-a1b2c3d4.css). There is no service provider, no facade, no config file in config/, and Assetic itself has been effectively abandoned since Symfony 3.x. With 2 stars, 0 dependents, nothing of note since March 2016, and the repo archived, treat this as a reference implementation of an idea, not a dependency.
What the latest release changed (v1.0.3), and why it doesn't change the verdict: the only addition is a documentation suggestion to enable Assetic's cached worker so a single asset that appears in multiple Twig templates isn't recompiled once per template. It is a performance tip for a build step, not a new API, not a Laravel affordance, and not a fix to the abandoned dependency chain. If you are not maintaining a legacy Symfony app, nothing in v1.0.3 is relevant to you.
What to look at first (5 minutes, not an afternoon):
composer.json — note the symfony/* and assetic/* requirements and the PHP constraint. This tells you immediately whether it can even resolve in a modern Laravel app.DependencyInjection/*Extension.php and Resources/config/services.* — this is the entire package. It wires one worker service into the Assetic factory.README.md — one config snippet showing how the worker is enabled, plus (as of v1.0.3) the note about the cached worker.First use case (the one you should actually copy): content-hashed filenames so you can serve Cache-Control: immutable for a year.
If you are maintaining a legacy Symfony app:
composer require jeroenvdheuvel/assetic-cache-busting-worker-bundle
// app/AppKernel.php
$bundles = [
// ...
new JeroenVdHeuvel\AsseticCacheBustingWorkerBundle\AsseticCacheBustingWorkerBundle(),
];
Then dump assets (php app/console assetic:dump --env=prod) and reference them through Twig's {% stylesheets %} / {% javascripts %} tags — the asset_url variable picks up the hashed name automatically.
If you are on Laravel, do not install it. Install nothing and instead reach for the tool you already have:
{{-- resources/views/layout.blade.php --}}
<link rel="stylesheet" href="{{ Vite::asset('resources/css/app.css') }}">
Vite/Mix already produce a manifest.json with content hashes — and Vite's build is already incremental, so you get the v1.0.3 "don't recompile the same asset twice" benefit for free. That is the 2016 bundle's entire value proposition, and you get it without the dependency.
The worker runs at dump time, not at request time. The shape is:
# app/config/config.yml
assetic:
# confirm in the bundle's DI extension whether the worker is
# auto-registered or must be named here for your version
workers:
cache_busting: ~
{% stylesheets 'css/app.css' filter='cssrewrite' output='css/*.css' %}
<link rel="stylesheet" href="{{ asset_url }}">
{% endstylesheets %}
asset_url becomes /css/app-<hash>.css after assetic:dump. The hash is derived from file contents, so unchanged files keep their name across deploys and stay warm in caches.
v1.0.3 addition — the cached worker. If the same stylesheet or script is pulled into several Twig templates (a shared base.html.twig plus a partial, say), Assetic will compile it once per template unless you enable its cached worker, which memoises compiled output so repeated references are cheap. Enable it alongside the cache-busting worker in the same assetic: block. Read the DI extension to confirm the exact key for your Assetic version — the release notes describe the intent, not a config schema, and the bundle itself ships no new service for it. The trade-off is real: a memoised compile means a filter/config change can keep serving the previous bytes under the previous hash, so flush the cache as part of any deploy where you touch assetic: settings (see Gotchas).
This is the day-to-day pattern you should actually own. A build command hashes files and writes a manifest; a helper resolves names at render time.
// app/Console/Commands/BuildAssets.php
public function handle(): int
{
$manifest = [];
foreach (File::allFiles(public_path('assets')) as $file) {
$relative = str_replace(public_path().'/', '', $file->getPathname());
$hash = substr(hash_file('xxh128', $file->getPathname()), 0, 8);
$hashed = preg_replace(
'/\.([^.]+)$/',
"-{$hash}.\$1",
$relative
);
File::ensureDirectoryExists(dirname(public_path($hashed)));
File::copy($file->getPathname(), public_path($hashed));
$manifest[$relative] = $hashed;
}
File::put(public_path('build/manifest.json'), json_encode($manifest, JSON_PRETTY_PRINT));
return self::SUCCESS;
}
// app/Support/helpers.php
function hashed_asset(string $path): string
{
static $manifest = null;
$manifest ??= json_decode(
@file_get_contents(public_path('build/manifest.json')) ?: '{}',
true
);
return asset($manifest[$path] ?? $path);
}
<link rel="stylesheet" href="{{ hashed_asset('assets/css/app.css') }}">
<script src="{{ hashed_asset('assets/js/app.js') }}" defer></script>
This is ~30 lines, has no dependency risk, and mirrors the bundle's semantics exactly (hash-on-content, resolve-by-manifest). Because it hashes each file once in a single pass, it is already immune to the duplicate-compile problem v1.0.3 is trying to paper over.
The whole point of the hash is to make the caching aggressive. Pair it with:
location ~* \.(css|js|png|jpg|jpeg|svg|woff2)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
And in your deploy pipeline, upload the hashed files to S3/CloudFront and keep only the manifest plus unhashed HTML in origin. Because the filename changes on content change, you never need to purge — you just deploy a new manifest.
| Concern | Practical approach |
|---|---|
| Blade/API consistency | One helper, resolved from the same manifest, used in Blade and in any JSON API that returns asset URLs. |
| CI | Run the hash step as a post-autoload-dump script so local and prod never diverge. |
| Rollbacks | Keep the previous manifest alongside the previous release directory; hashed files are additive, so rollback is a symlink swap. |
| Dev vs prod | In local, skip hashing entirely and serve source paths — hashes just make stack traces harder to read. |
| Testing | Assert the manifest contains every key your views reference; a missing key silently falls back to an unhashed (404ing) path. |
| Duplicate compiles (the v1.0.3 concern) | Build once, resolve everywhere. A single manifest written by one command is the Laravel-native fix; you should never be compiling the same source per view. |
Bundle class is only instantiated by a Symfony kernel. There is no Illuminate\Support\ServiceProvider, so Laravel's container never sees it. Any time spent trying to bootstrap it is wasted.symfony/assetic-bundle was deprecated in Symfony 3.0 and removed as a recommendation in 4.0; Symfony's own answer is Webpack Encore, and Laravel's is Vite. Depending on this package in 2024+ means pinning a chain of abandoned packages.cssrewrite setting and the previously compiled artifact can be served back with a matching (now wrong) content hash. Flush the Assetic cache in the same deploy step that changes assetic: config — the bundle does not do this for you.each()/create_function style deprecations, and zero security response. If you must run it, vendor it or fork it; do not rely on Packagist availability long-term.dev, the worker doesn't run, so asset_url looks different from prod. Bugs of the "works locally, 404s in prod" variety almost always come from a view hardcoding an unhashed path that only exists in the dev pipeline.manifest.json pointing at a file that wasn't uploaded (or vice versa). Validate in the deploy step: every manifest value must exist on disk.?v=abc123 (Laravel's mix()/Vite style for older versions, or a naive asset().'?v='.$hash) works, but some CDNs and proxies strip query strings for caching. Filename hashing — what this bundle does — is strictly more robust.jeroenvdheuve/ and sometimes jeroenvdheuvel/; check the actual name field in composer.json before require-ing, or you'll chase a phantom package-not-found.composer.json.How can I help you explore Laravel packages today?