maba/webpack-bundle
Symfony bundle that integrates Webpack into your application, offering Twig helpers and services to load compiled assets, resolve manifest entries, and use the Webpack dev server. Simplifies asset bundling and cache-busting in Symfony projects.
Start with the reality check: maba/webpack-bundle is a Symfony bundle (Maba\Bundle\WebpackBundle). There is no WebpackServiceProvider, no Blade directive, no artisan command, and no config/webpack.mix.js. If you are building a Laravel-only app, the correct day-to-day answer is "use Vite or Laravel Mix." The package is worth your time only in one scenario: you like its manifest contract and Twig helper semantics and want to port them to Blade, or you work in a shop that has both a Symfony app and a Laravel app and want one shared Webpack build output.
If you proceed, here is the minimal path:
composer require maba/webpack-bundle
Register it in config/bundles.php, then configure manifest location:
# config/packages/maba_webpack.yaml
maba_webpack:
paths:
- '%kernel.project_dir%/public/build/manifest.json'
That manifest is the whole contract: a JSON map of entry name → { js: [...], css: [...] } (or a flat file list, depending on the plugin generating it). The bundle's job is to read that file and emit <script> / <link> tags per entry, with correct hashed filenames.
Where to look first: the README's Twig examples (webpack_entry_js_files, webpack_entry_css_files, webpack_entry_script_tags, webpack_entry_link_tags), then DependencyInjection/Configuration.php for the real config keys, then the Twig extension and manifest reader classes — those two files are what you will reimplement in Laravel.
First use case to port: a Blade directive that renders every JS/CSS file for a named entry, so a controller returns view('dashboard') and the layout calls @webpackEntry('dashboard'). Do this before touching dev-server or hot-reload features.
One entry per page, plus a shared vendor entry. This is the pattern the bundle is built around and the one worth copying. webpack.config.js declares app, vendor, dashboard, checkout; the manifest tells PHP which hashed files each one needs.
Port the reader, not the bundle. A small Laravel service is enough:
// app/Support/WebpackManifest.php
class WebpackManifest
{
private ?array $data = null;
public function __construct(private string $path) {}
public function entry(string $name): array
{
$this->data ??= json_decode(file_get_contents($this->path), true, flags: JSON_THROW_ON_ERROR);
// webpack-manifest-plugin v2/v3: { "app.js": "/build/app.abc.js" }
// webpack-assets-manifest / newer formats: { "app": { "js": [...], "css": [...] } }
if (isset($this->data[$name]) && is_array($this->data[$name])) {
return $this->data[$name] + ['js' => [], 'css' => []];
}
return ['js' => array_values(array_filter(
$this->data, fn ($v, $k) => str_starts_with($k, $name.'.') && str_ends_with($k, '.js'), ARRAY_FILTER_USE_BOTH
)), 'css' => []];
}
}
Bind it as a singleton with the manifest path from config('webpack.manifest').
Blade directives mirroring the Twig helpers:
Blade::directive('webpackEntry', fn ($expr) => "<?php echo app(\App\Support\WebpackManifest::class)->entry($expr)['js'] ... ?>");
Keep two directives (@webpackScripts, @webpackStyles) rather than one mega-directive — you will want scripts in <head> with defer and styles separately, exactly as webpack_entry_script_tags / webpack_entry_link_tags split them in Twig.
Split JS and CSS consumers. Not every entry has CSS. Return empty arrays, not null, and let the directive emit nothing. This is what the bundle does and it is why webpack_entry_css_files('app') is safe to call on a JS-only entry.
Dev vs production via env, not code. Point webpack.manifest at a dev manifest in local and the hashed production manifest in CI. Never branch on app()->environment() inside the reader; the manifest itself is the switch.
Cache-busting is the manifest's job, not Laravel's. Do not append ?v=filemtime(). The hashed filename in the manifest is your cache key. If you find yourself adding query strings, you have two cache layers fighting.
CI ordering: npm ci && npm run production before php artisan config:cache and before whatever copies public/build into the release. The manifest must exist at boot time if you bind its path eagerly.
composer why-not before planning around it.webpack-manifest-plugin changed its output shape across major versions (entrypoints as object vs. array; flat file map vs. per-entry object). The bundle was written against one of them. Log the decoded manifest once during setup and match your reader to what you actually see — do not match it to a blog post.asset/resource paths the reader does not expect, that is a build-side problem, not a PHP-side one./build/app.abc.js (URL). Do not run it through asset() blindly — you may double-prefix or lose a CDN base. If you need a CDN, rewrite the prefix in one place in the reader.tests/fixtures/manifest.json with one entry containing both JS and CSS covers 90% of your reader's branches and keeps tests green without Node installed.<?php ?> output but your attributes can still be injected from entry names. Whitelist entry names or escape attributes.How can I help you explore Laravel packages today?