grizzlylab/assets-version-bundle
GrizzlyLab AssetsVersionBundle adds automatic versioning to your Symfony asset URLs, appending a configurable version or hash query string so browsers fetch updated files after each deploy. It helps prevent stale CSS, JavaScript, and image caching with minimal configuration.
Start by confirming the obvious: this is a Symfony 2/3 bundle, not a Laravel package. No service provider, no facade, no config/*.php, no package discovery. composer require grizzlylab/assets-version-bundle in a Laravel 10/11 app will at best install dead weight and at worst conflict on Symfony 2/3-era platform constraints. Do not attempt to boot it from Laravel.
The right "getting started" is to read it as reference material and port the idea:
Minimal path:
composer.json.DependencyInjection/*Extension.php and Resources/config/services.* — shows the two moving parts (a console command and a Twig helper).?v={version} to asset() output.The first use case worth building is the one the bundle exists for: on deploy, bump a single version token so every asset() URL changes and browser/CDN caches bust — without renaming files or re-running a manifest build. A minimal native equivalent to aim for:
// config/app.php
'asset_version' => env('ASSET_VERSION', '1'),
// app/Helpers/assets.php (or a Blade directive)
function versioned_asset(string $path): string
{
return asset($path) . '?v=' . config('app.asset_version');
}
Wire ASSET_VERSION into CI so each deploy emits a build number or short Git SHA, then use versioned_asset() (or @versionedAsset('css/app.css')) in layouts. That is the whole feature — you do not need the bundle to get it.
Choose the pattern based on what you actually need, not what the bundle happens to implement.
Pattern A — env-var token (recommended default). Emit ASSET_VERSION from CI (GITHUB_RUN_NUMBER, short SHA, or a timestamp) and read it through config('app.asset_version'). Zero runtime mutation, zero concurrency risk, reproducible builds. Use this unless you have a specific reason not to.
Pattern B — command-driven bump (port of the bundle's model). A php artisan assets:bump command reads and writes a persisted version (in .env, a JSON file under storage/, or a database row) and is called from the deploy pipeline. This is what the Symfony bundle does. Prefer it only when the version must live in the repo or be readable by non-CI tooling. Note that writing to .env at deploy time is a smell — writing to storage/app/asset-version.txt (gitignored) is cleaner.
Pattern C — helper + Blade directive. Expose the versioned URL helper as a Blade directive so templates stay readable:
Blade::directive('versionedAsset', fn ($expr) => "<?php echo versioned_asset($expr); ?>");
Pattern D — coordination with CDN purges and rollouts. The single global token is a release identifier, which is its most defensible use in Laravel. Emit the same token to your CDN purge step and to error trackers (as a release tag), so a stale asset report can be traced to a specific deploy. That is genuinely useful even when your static files are content-hashed by Vite.
Pattern E — hybrid with Vite. Do not replace @vite with a global token — that would be a regression (per-file hashes invalidate only changed files). The bundle's pattern fits alongside Vite: Vite handles static asset freshness; the asset-version token serves as your release identifier for cache warmers, purge scripts, and canary gating.
Integration tips:
Cache-Control: public, max-age=31536000, immutable on files whose URL carries a per-file hash (Vite). Reserve the ?v= token for cases where you control only the URL, not the filename./health so QA can verify which build is deployed.assets:bump with no argument should rotate; assets:bump v123 should pin.Do not install it. PHP 8.x + Symfony 2/3 constraints will block composer install; forcing with --ignore-platform-reqs leaves autoloadable classes that will fatal at runtime and give a false sense of capability. Nothing here requires the package.
Global token means global invalidation. A single version string invalidates every cached asset on every deploy — all users, all CDN PoPs, all at once. On a non-trivial traffic base that is a thundering herd at origin. Vite's content-hashed filenames invalidate only what changed. If you adopt the token pattern, be honest about this tradeoff; it is appropriate for tiny asset sets that change together, not for large graphs.
Config-mutation races. If you port the command's write-to-config behavior, parallel deploys can collide, and a dirty working tree after deploy breaks reproducibility. The env-var approach sidesteps this entirely.
Config cache and opcache. In Laravel, php artisan config:cache bakes env vars into a cached file. If your version comes from .env, you must re-run config:cache (or config:clear) after changing the value, or the app serves the old token. This is the exact class of bug the Symfony bundle's cache:warmup dependency causes — same failure mode, different framework. Prefer reading the version directly via env() only in config/, and always rebuild config in the deploy script.
Half-applied deploys. The classic failure: token bumped but assets not yet uploaded (or vice versa) → 404s or stale JS in production, which surface as phantom client bugs. Sequence your deploy as: upload assets → verify → flip the version → purge CDN. Never the reverse.
Debugging. When a user reports stale CSS, first check the served URL's ?v= value against the current ASSET_VERSION. If they match, the issue is CDN edge caching, not the token. Add the version to a response header (e.g. X-Asset-Version) during rollout to make this a one-glance check.
Extension points to plan for. If you build the helper, design for: (a) a config key that can be overridden per environment; (b) a fallback to APP_VERSION/Git SHA when ASSET_VERSION is unset, so local dev just works; (c) an optional per-file-hash mode that reads Vite's manifest.json when it exists, so the helper does the right thing in both setups; (d) a php artisan assets:version command that prints the current token — useful for CI checks and debug.
Testing. The helper is trivially testable: assert that the returned URL contains the current version and that changing the config changes the URL. The command is testable with Storage::fake() if you go the file-backed route. Budget an hour for both, not a sprint.
Security/supply-chain. Even if the concept is useful, the package has 0 stars, 0 dependents, a single maintainer, and targets an EOL framework. Reimplementing the ~100 lines natively is strictly cheaper than accepting an unmaintained build-path dependency with no security response process.
How can I help you explore Laravel packages today?