wyrihaximus/js-compress
Compress and minify JavaScript in PHP with wyrihaximus/js-compress, a small library that wraps a JS compressor behind a simple API so you can shrink assets during your build or at runtime.
composer require wyrihaximus/js-compress only installs the PHP side; this package is a thin wrapper around an external js-compress binary driven through symfony/process. Before writing any code, open the package README and composer.json and check whether the binary is bundled, downloaded by a Composer script, or expected on PATH. Then verify with which js-compress (or the path the README documents) inside the same container/CI image that runs your app.vendor/wyrihaximus/js-compress/src/ — the public surface is small, usually a single Compressor class (implementing a CompressorInterface from a companion wyrihaximus/compress abstraction) exposing compress(string $js): string. Skim its tests: they're the fastest way to see exactly what input the binary receives and whether it shells out per call, writes temp files, or streams via stdin.app(\WyriHaximus\JsCompress\Compressor::class)->compress("function add(a, b) {\n return a + b;\n}") and confirm you get back a single line. If that throws, the problem is the binary, not your code — fix that before building anything on top.@if-gated inline scripts emitted by a Blade component, or vendor JS files copied into public/ that your build pipeline never touches. Start there — a small Artisan command that minifies one file to *.min.js is the cheapest proof of value.Container binding + interface injection. Don't new Compressor() inline. Bind the interface as a singleton in a service provider so you can swap it for a no-op in tests:
$this->app->singleton(\WyriHaximus\Compress\CompressorInterface::class, fn () => new \WyriHaximus\JsCompress\Compressor());
Build-step command, not request-time. The natural home for this is a console command run from composer.json scripts or your deploy pipeline:
foreach (File::glob(public_path('js/vendor/*.js')) as $path) {
File::put(
preg_replace('/\.js$/', '.min.js', $path),
$this->compressor->compress(File::get($path))
);
}
Wire it as php artisan js:compress and call it in the post-autoload-dump or your CI "build" step, next to npm run build. Keeping it out of the request lifecycle avoids paying a process-spawn per pageview.
Cache aggressively by content hash. If you must minify at runtime (DB-stored snippets, CMS-authored JS), never do it twice for the same input:
$minified = Cache::remember(
'js.min.'.hash('xxh128', $source),
now()->addDay(),
fn () => $this->compressor->compress($source)
);
Content-hash keys mean edits invalidate naturally, and you get a free record of which snippets are actually hot.
A Blade component for inline scripts. Encapsulate minification in a <x-inline-js :code="$snippet" /> component that no-ops outside production (app()->isProduction()) and otherwise pipes through the compressor. This keeps debug-friendly output locally and terse output in prod without scattering conditionals through templates.
Batch large sets via a job. If you're minifying hundreds of vendor files, dispatch a queued job (or Bus::batch) so the deploy isn't blocked and a single failure is retryable and isolated.
Response macro for one-off routes. For routes that serve a generated .js file (feature flags, config blobs), a Response::macro('js', ...) that sets Content-Type: application/javascript and runs the compressor on the payload is tidier than a controller method per file — but only if you can cache it, per the note above.
composer install and a hard failure in production or CI because the binary isn't in the image. Bake it into your Dockerfile and pin the version; add a php artisan js:compress --check style preflight that exits non-zero when the binary is missing so deploys fail loudly instead of silently shipping unminified code.compress() call forks a process. That's ~10–100ms of overhead before any actual work — fine for a build step, ruinous inside a loop on a hot route. Batch inputs, or push to a queue.app()->isProduction() or during the build, otherwise you'll be reading stack traces pointing at line 1, column 4000..min.js files are already compressed; running them through again wastes time and occasionally breaks regex-heavy code. Filter on file name in your glob.blank($js) before calling, and assert in a test that an empty input returns an empty string rather than blowing up a deploy..cmd/.exe and a forward-slash-normalised path. This is a common reason the package "works on my machine."CompressorInterface, you can drop in a Terser or esbuild-backed implementation (shelling out to npx) without touching call sites — worth doing if the packaged binary lags on modern syntax. Likewise, bind a FakeCompressor returning input unchanged in your test suite so tests stay fast and don't require the binary.// comments" or "output is shorter than input" are stable; exact-string assertions on minified output will break on every binary bump.How can I help you explore Laravel packages today?