rumenx/php-assets
Rumenx PHP Assets is a lightweight PHP library for managing frontend assets. It helps compile, combine, minify, version, and cache CSS and JavaScript files, giving PHP and Laravel applications a simple way to organize and serve optimized assets.
Start by installing the package and verifying what it actually manages for you versus what your build tool already handles.
composer require rumenx/php-assets
Where to look first:
php artisan vendor:publish --tag=php-assets-config (or the package's equivalent tag from its README). This is the single source of truth for asset roots, manifest location, CDN base URL, and cache busting strategy. Read it top to bottom before writing any code.Assets:: facade or a global asset_versioned() style helper. Most Laravel developers will only ever touch this one API.config/app.php (it usually is auto-discovered on Laravel 9+).First use case: Replace asset('css/app.css') with the package's versioned call in resources/views/layouts/app.blade.php. That is the smallest possible change that gives you cache busting across deployments without pulling in Vite's full manifest tooling.
{{-- before --}}
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
{{-- after --}}
<link rel="stylesheet" href="{{ Assets::url('css/app.css') }}">
Once that renders a hashed or query-string URL, you've validated the whole pipeline (config → manifest → URL resolver). Everything else is incremental.
Wrap the package behind a Blade component or partial so your views never call the package directly. When you swap to Vite, a CDN, or a different asset tool, you edit one file.
{{-- resources/views/components/assets.blade.php --}}
@props(['entry' => 'resources/js/app.js'])
<link rel="stylesheet" href="{{ Assets::url($entry) }}">
<script src="{{ Assets::url($entry) }}" defer></script>
Usage: <x-assets entry="resources/css/app.css" />.
Point the config at your build manifest (public/build/manifest.json or public/mix-manifest.json). The package resolves logical names to hashed filenames. This is the pattern most Laravel teams will use because it lets you keep Vite for building but resolve URLs on the server side in places Vite's @vite directive doesn't cover (emails, PDFs, API responses that embed asset URLs, non-Blade templates).
// config/php-assets.php
return [
'manifest' => public_path('build/manifest.json'),
'strategy' => 'manifest',
];
Use the environment to pick a versioning strategy: query-string in local, manifest in staging/production, CDN prefix in production. Keep this in a dedicated config key rather than branching in code.
'strategy' => env('ASSET_STRATEGY', app()->environment('production') ? 'manifest' : 'query'),
'cdn' => env('ASSET_CDN_URL'), // null in local, e.g. https://cdn.example.com in prod
Then Assets::url() automatically picks the right behavior. No if (app()->environment(...)) scattered across views.
app(), in application codeWhere the package supports DI (most do), resolve the asset manager from the container in controllers, jobs, and mailables rather than using helpers. This makes tests trivial — swap in a fake implementation that returns predictable URLs.
public function __construct(private Assets $assets) {}
public function show(): View
{
return view('show', [
'thumbnail' => $this->assets->url('img/placeholder.png'),
]);
}
If the package supports named groups (e.g. Assets::group('admin')->all()), use them for admin vs. public layouts. Groups keep the "which CSS do I load on this page" question out of the templates.
@vite where it already fitsDo not replace @vite in Blade templates that already work — it handles HMR, preloads, and module scripts correctly. Use the package for:
og:image URLs.mix()/asset().This is the highest-value usage pattern for an existing Laravel app: it's a bridge, not a replacement.
If the package exposes <link rel="preload"> generation or lets you set attributes, generate those in a service provider or view composer and attach the CSP nonce from your middleware.
$preloads = $assets->preload(['fonts/inter.woff2']);
Render them in <head> before any stylesheets.
public_path('build/manifest.json') is evaluated when the container boots. If you cache config (php artisan config:cache) and run config:cache before a build runs in CI, the manifest path may be correct but the contents are read lazily — check whether the package caches the parsed manifest in memory per request. If it does, long-running workers (Horizon, Octane, queue workers) will serve stale hashes until restarted. Add a deploy hook: php artisan octane:reload (or queue:restart) after assets are built.
The classic failure: production is fine, staging renders broken asset URLs. Causes:
.env not copied).curl against the rendered HTML, not just the browser.Rule: after every deploy, run curl -s https://staging.example.com | grep -o 'href="[^"]*"' and confirm the URLs resolve.
Most asset packages do rtrim($cdn, '/') . '/' . ltrim($path, '/') — but some don't. If you see https://cdn.example.com//css/app.css (double slash), that's your cue to check the package's URL builder. Some CDNs tolerate it, Cloudflare doesn't always.
If the package's Assets::url() returns ?v=abc123 but @vite returns /build/assets/app-abc123.js, you have two versioning systems. Pick one per view. Mixing them causes double-loading of the same asset under different URLs, which blows up cache hit rate and can cause duplicate-side-effect bugs (two Vue instances, two analytics scripts).
If you develop on Windows and deploy to Linux, backslashes in the manifest (rare but happens with some bundlers) will break str_replace-based path normalization. Check that the package uses DIRECTORY_SEPARATOR or str_replace('\\', '/', ...). If not, normalize the manifest keys yourself in a small custom resolver.
Your server may cache the parsed manifest for N seconds (check config for a cache_ttl key). When you deploy new assets, the manifest changes but the server keeps serving old URLs. Solutions:
Don't guess — grep the package source for cache-> calls.
In feature tests, bind a fake asset manager that returns deterministic URLs (/fake-assets/foo.css). This keeps tests fast and independent of whether npm run build ran. Only the single integration test that exercises the real resolver should touch the filesystem.
$this->app->instance(Assets::class, new class implements Assets {
public function url(string $path): string { return "/fake/{$path}"; }
});
config for a drivers array.AssetResolved / AssetMissing events. Hook AssetMissing to log rather than throw in production, so a single missing image doesn't 500 the page.Macroable trait, add application-specific helpers (Assets::ogImage()) in a service provider instead of subclassing.If you're already using Vite in a standard Laravel app and only need Blade templates, @vite covers you — this package earns its keep when you have non-Blade render paths, multiple build tools, or a CDN layer you want to manage centrally. If you're on Vite 5+ with a monorepo, look at laravel-vite-plugin's multi-entry support before adding a second asset layer.
The last release is recent (Sept 2025) and the package is at 0.x-level maturity by dependent count (0). Pin to an exact minor version in composer.json ("rumenx/php-assets": "~1.2.0") and commit composer.lock. Read the changelog before every bump — asset URL generation is the kind of thing that produces silent visual regressions, not exceptions.
How can I help you explore Laravel packages today?