zenstruck/asset-manifest-bundle
Symfony bundle that reads a JSON asset manifest (like Webpack Encore's manifest.json) to resolve versioned, hashed asset paths at runtime, so templates reference correct filenames without hardcoding them.
Read this first: zenstruck/asset-manifest-bundle is a Symfony bundle, not a Laravel package. There is no ServiceProvider, no facade, no publishable config, and no auto-discovery. It was last released in December 2016, is archived, and has zero dependents — so the realistic "getting started" for a Laravel developer is: do not install it. Laravel already ships the same capability through Vite (@vite, Vite::asset()), and this bundle targets Symfony 2/3-era APIs that will collide with the Symfony 6/7 components modern Laravel pulls in.
If you're assessing it because you have a legacy Symfony service or you're porting an app, the minimal path is:
composer require zenstruck/asset-manifest-bundle in the Symfony app only.AppKernel/bundles.php.manifest_path config at your generated manifest (e.g. web/build/manifest.json).Where to look first: the bundle class and its DependencyInjection/ extension. The whole package is effectively one small service that json_decodes a manifest file and maps a logical asset name to a hashed output path. That is the entire value proposition — about 30 lines of code.
First Laravel use case (the reason you're here): replacing asset('css/app.css') with a lookup against public/build/manifest.json so hashed filenames resolve correctly in production. Do this with Vite's built-in helpers; only hand-roll it if you're consuming a non-Vite manifest (Webpack Encore, Rollup, a CMS-generated map).
Treat the bundle as a reference implementation, then implement the pattern natively in Laravel.
__construct(string $manifestPath) and resolve(string $entry): string. Parse once, cache in a property, expose a clear failure mode.AppServiceProvider so the JSON is read once per request/worker, not per Blade render.manifest_asset('images/logo.png')) or a custom directive @asset('...'). Keep HTML escaping at the Blade layer, not in the resolver.URL::asset() or prepend config('app.asset_url'). Don't bake absolute URLs into the manifest.entrypoints.json) and Vite (manifest.json), write one adapter that emits a common { entry: { file, css, imports } } shape so the helper has a single contract.Integration tip: bind your reader and inject it where needed rather than calling a global helper from services; helpers are fine at the Blade edge only.
{"app.js": "app.abc123.js"}); Vite's manifest is nested ({"resources/js/app.js": {"file": "...", "css": [...], "imports": [...]}}). Copy-pasting the bundle's lookup logic into Laravel will break on Vite immediately.public/. Don't double-prefix /build/ or you'll get /build/build/app.abc.js.file_get_contents per call, but also don't Cache::rememberForever without invalidation — you'll serve stale hashes after a release. Key the cache by manifest mtime or app version.public/ symlink can resolve to the previous release's manifest. Clear/rebuild per deploy.URL::asset(), breaking apps served under /subdir.Illuminate\Foundation\Vite or bind your own manifest reader. Do not attempt to bridge Symfony's asset packages into Laravel.How can I help you explore Laravel packages today?