Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Assets Laravel Package

botble/assets

Botble Assets is a Laravel package for managing frontend assets in Botble CMS projects. It helps register, organize, version, and render CSS and JavaScript files from themes and plugins, simplifying asset publishing and dependency handling.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and decide if you actually need it. Run composer require botble/assets. In a fresh Laravel app you already have Vite; this package is worth adding when you need a registry of named handles with ordering/dependencies shared across packages, themes, and plugins — not when you just need to ship one app bundle.

Look at these files first, in this order:

  1. config/assets.php (published via php artisan vendor:publish --tag=assets-config; the tag name varies by version — run php artisan vendor:publish and read the list if it's not there).
  2. The manager class in src/ (typically Assets.php / AssetsManager.php) — this is the real API surface; the Facade is a thin proxy. Read the public method signatures before trusting any doc snippet, including this one.
  3. The service provider, to see how the singleton is bound and what config keys it reads.

Register, then render. The minimal first use case is a single stylesheet and script in your layout:

// app/Providers/AppServiceProvider.php — boot()
use Botble\Assets\Facades\Assets;

Assets::addStyles([
    'app' => asset('css/app.css'),
]);

Assets::addScripts([
    'app' => asset('js/app.js'),
]);
{{-- resources/views/layouts/app.blade.php --}}
<head>
    {!! Assets::renderHeader() !!}
</head>
<body>
    ...
    {!! Assets::renderFooter() !!}
</body>

Verify by dumping the registry (Assets::getStyles()) and inspecting the <head> output. If nothing renders, you're almost certainly registering after the layout has rendered — see Gotchas.

The mental model: you register keyed entries ('app' => [...] or a flat list), the manager sorts them, and two render methods emit the HTML. Everything else — dependencies, attributes, building, versioning — is decoration on that core loop.

Implementation Patterns

Central registry per module, not per view. The package's value appears when you stop sprinkling <link> tags. Give each package/theme/module a service provider that registers its own handles:

// packages/Admin/src/Providers/AdminServiceProvider.php
Assets::addStyles([
    'admin' => [
        'src'     => asset('vendor/admin/css/admin.css'),
        'deps'    => ['app'],           // ordering, not autoloading
    ],
]);

Now a page can say Assets::addStyles(['admin']) and get the transitive stylesheet without knowing its path.

Use addStyles vs addStylesDirectly deliberately. The distinction is the single most important design decision in this package: the non-Directly variants feed into the concatenation/build pipeline (and, when build mode is on, disappear from individual <link> output), while the Directly variants always render as their own tags. Practical rule:

  • addStyles / addScripts — first-party code you control, safe to bundle.
  • addStylesDirectly / addScriptsDirectly — CDN assets, anything with an integrity hash, anything that must not be concatenated, and anything added at runtime (e.g. from a middleware or an admin-bar plugin).

Getting this wrong is the classic "works locally, blank page in production" bug when build mode flips on.

Attach attributes at registration time, not by string surgery.

Assets::addScriptsDirectly([
    'alpine' => [
        'src'    => 'https://cdn.example.com/alpine.js',
        'attributes' => ['defer' => true, 'crossorigin' => 'anonymous'],
    ],
]);

This keeps escaping centralized. Never build <script> strings yourself and pass them in — you lose ordering and you'll double-escape.

Page-specific assets belong in the controller or a view composer, not the layout. A view composer scoped to a route group is the cleanest integration point:

View::composer('checkout.*', function () {
    Assets::addScriptsDirectly(['stripe' => 'https://js.stripe.com/v3/']);
});

Because the composer runs before the view renders, renderFooter() picks it up. This is the pattern that scales — you never touch the layout again.

Override and remove instead of forking. removeStyles('handle') / removeScripts('handle') after all providers have booted lets a child theme or downstream app swap an asset out. Wrap this in a provider registered last (or use booted() on the app) so it wins the race.

Versioning / cache busting. Most versions expose a setVersion()-style method or read a version key from config. Set it once per deploy — from a build id, git rev-parse, or a manifest. Avoid md5_file() on every request for large bundles; it's disk I/O on the hot path and defeats the point of a static build. Compute it at deploy time into config or a cached file.

Extend through macros on the manager. If you find yourself writing the same three-line registration repeatedly, add a macro (or a small wrapper service) like Assets::addAdminAssets() that registers the whole group. Macros survive package upgrades; subclasses of the manager generally don't.

Test the rendered output, not the internals. A feature test asserting $response->assertSee('js/app.js') and correct ordering catches 90% of regressions. Asserting Assets::getStyles() array shape is brittle.

Gotchas and Tips

Render timing is the #1 failure mode. renderHeader() / renderFooter() are snapshots — anything registered after the call is silently dropped. Providers register in boot(); if you register from a controller action (after the middleware stack), it's usually fine, but from an after middleware or a terminating callback it is not. Symptom: asset appears in getStyles() but never in HTML. Fix: move registration earlier or use a view composer.

Echo, don't escape. {!! !!} is mandatory; {{ }} gives you visibly escaped <link> tags. Conversely, if the package returns rather than echoes in your version, {!! !!} around a null return produces a blank line — harmless, but confusing when you're diffing output.

Ordering is topological, so a typo'd dependency fails silently. A 'deps' => ['jquerry'] entry typically won't throw — the handle just doesn't exist and ordering falls back. If scripts fire in the wrong order, dump the registered keys and compare against your deps strings character by character.

config:cache + closures. If your config/assets.php contains any closure, php artisan config:cache throws. Keep callbacks (callbacks that mutate the asset list) in service providers, keep the config file pure scalars/arrays. Also note config changes vanish under a stale config cache — always php artisan config:clear when debugging "my config isn't applied."

Don't run Vite and this package on the same asset. @vite('resources/js/app.js') plus Assets::addScripts(['app']) gives you two copies of Alpine/React and two HMR clients in dev. Pick one per asset. A workable split: Vite owns the frontend app bundle, this package owns everything injected by packages, plugins, and dynamic admin UI.

Build/offline mode changes the output shape. Switching the package into build mode collapses registered files into a generated bundle. Any asset registered through the non-Directly method disappears as a standalone tag. If your CSS relied on being loaded before an inline style block, or your script relied on a specific position relative to a CDN tag, that ordering can shift. Always smoke-test with build mode enabled in staging — it's the only way this class of bug surfaces.

defer/async and renderFooter() interact. defer scripts preserve execution order among themselves but still run before DOMContentLoaded; inline initialization written immediately after a <script> tag will not see them. If you add defer, move initialization into a DOMContentLoaded listener or an explicit init call.

SPAs and Livewire break naive assumptions. Turbolinks/Livewire navigation swaps the body without re-running <head> tags. Assets registered on the second page never load. Register the union of assets at the layout level, or dispatch an event on navigation to re-inject.

Path vs URL. Leading-slash handling differs between asset() output and raw strings passed in. Always funnel paths through asset() (or the package's path helper) rather than writing 'css/app.css' — relative paths resolve against the current request URI and break on nested routes like /admin/users/1/edit.

Inline scripts and CSP. If you're on a strict Content-Security-Policy, per-request nonces must be injected into every tag the package renders. Check whether your version supports an attributes array on inline registrations; if not, the practical escape hatch is to render those few tags yourself and disable the package's handling of them.

Debugging recipe: dump Assets::getStyles() and Assets::getScripts() from a temporary route, diff against what appears in view-source, and check the rendered order rather than the registered order. The gap between those two lists tells you whether the problem is registration, filtering (build mode), or rendering.

Low-activity package caveat. With ~46 stars and a small maintainer surface, expect method names and config keys to drift between minor versions and expect little Stack Overflow coverage. Pin the version in composer.json, keep your registrations behind a thin wrapper of your own (a single AssetRegistrar class), and upgrading becomes a one-file change instead of a codebase-wide find-and-replace.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums