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

Google Closure Bundle Laravel Package

jms/google-closure-bundle

JMS Google Closure Bundle integrates Google Closure Compiler with Symfony, providing services and console commands to compile, optimize, and minify JavaScript assets. It helps streamline frontend asset builds and reduce script size using Closure’s optimizations.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: it is not a Laravel package. jms/google-closure-bundle is a Symfony2-era bundle (2011–2013). There is no service provider, no facade, no config/*.php publish stub, and no bundles.php. It will not composer require cleanly into a Laravel 10/11 app — its transitive constraints on symfony/* 2.x will collide with the Symfony 6/7 components Laravel depends on. Treat any install attempt as a time-boxed spike, not a task.

What is actually reusable. Strip the Symfony wrapper and what remains is: shell out to compiler.jar (and optionally the Soy template compiler) during a build step. That is roughly 150 lines of process invocation. That idea — not the code — is what you take into your Laravel app.

Recommended starting point (Laravel-native):

npm install --save-dev google-closure-compiler

Then wire it into your existing Vite pipeline (laravel-vite-plugin) as a build-time step, or call the CLI directly:

npx google-closure-compiler \
  --js 'resources/js/**/*.js' \
  --js_output_file 'public/build/app.min.js' \
  --compilation_level SIMPLE

First use case to prove value: take one existing JS entry point, run SIMPLE-level compilation, diff the output size against your current raw/esbuild build, and confirm the app still boots. Do not start with ADVANCED.

Where to look first, if you are reading the bundle for reference:

  1. DependencyInjection/Configuration.php — the real config surface (java binary path + jar path, plus an options array).
  2. Resources/config/services.xml — the Assetic filter alias (closure) and the compiler service id.
  3. The README — the only docs that exist, and they are terse.

If you are maintaining a legacy Symfony2 app that already uses this bundle (the only credible reason to touch it): install a JRE, download a pinned compiler.jar from Maven Central (com.google.javascript:closure-compiler), register the bundle in AppKernel.php, configure jms_google_closure: { java: /usr/bin/java, compiler_jar: ... }, then use filter='closure' in Twig {% javascripts %} blocks and dump with app/console assetic:dump --env=prod --no-debug.

Implementation Patterns

Do not mix pipelines. The pattern that has caused the most pain in real projects is a half-Assetic, half-Vite setup where some templates reference asset_url and others reference @vite. Pick one, get it working end-to-end, and only then consider adding Closure as an extra optimization pass on top.

Pattern 1 — the default for a Laravel app: esbuild/terser via Vite. For 95% of Laravel apps this is the correct answer. laravel-vite-plugin already minifies in production. If you need more aggressive dead-code elimination than esbuild provides, add terser as the minifier. Zero new infrastructure, no JVM, no fork.

Pattern 2 — Closure Compiler for ADVANCED mode specifically. Use the npm google-closure-compiler package (it bundles a native binary — no JVM required on dev machines) inside a custom Vite plugin or a standalone scripts/compile-closure.js. Typical shape:

// scripts/compile-closure.js
import closure from 'google-closure-compiler';

new closure.compiler({
  js: ['resources/js/**/*.js', '!resources/js/**/*.test.js'],
  compilation_level: 'ADVANCED',
  js_output_file: 'public/build/app.min.js',
  externs: 'resources/js/externs.js',
  warning_level: 'VERBOSE',
  dependency_mode: 'PRUNE',
}).run((code) => process.exit(code));

Then invoke it from package.json scripts and chain it after vite build in the deploy step. Keep it out of the per-request path — it is a build artifact generator.

Pattern 3 — the thin PHP Process wrapper (only if you must stay in PHP). If your deploy tooling is PHP-only and you cannot run Node in CI, hide the invocation behind an interface so it can be swapped later:

interface JsCompiler { public function compile(array $inputs, array $options = []): string; }

Back it with a Symfony\Component\Process\Process call to compiler.jar, pin the JAR version, and cache output keyed on a hash of inputs. Register it in a service provider and bind it in the container. This is the minimum-survivable integration — not what the JMS bundle gives you.

Pattern 4 — Soy / Closure Templates. If your app genuinely renders Soy templates server-side (rare outside old Closure-stack apps), scope this as a separate pipeline decision. The soy npm toolchain is the maintained path; do not try to route Soy through the PHP bundle.

Environment-aware behavior. Whatever route you take, apply the compiler only in production builds. In dev you want fast hot reload, not a JVM start on every save. In Laravel this is trivially if (app()->environment('production')) around the build step; the JMS bundle achieves the same with an assetic.use_controller toggle plus a --watch workflow, which is why teams historically ended up with drift between dev and prod.

Migration pattern from a legacy JMS/Assetic app: inventory which bundles depend on the closure filter → stand up the new build (Vite or script) in parallel → diff artifacts in staging → cut over per-bundle → delete the old filter config. The risk surface is ADVANCED mode, not SIMPLE/WHITESPACE.

Gotchas and Tips

Composer will fight you. composer require jms/google-closure-bundle in a modern Laravel app fails on symfony/* version conflicts. Do not use --ignore-platform-reqs to force it — you will get a container that boots and then breaks in obscure, unrelated places. If you need to inspect the bundle's config surface, install it into an isolated Symfony2 sandbox or just read the source on GitHub.

There is no service provider, so nothing auto-registers. Even if the classes were compatible, Laravel's container would never boot the bundle's services. The Assetic tags, the Twig closure filter alias, the console command — none of it exists in a Laravel request lifecycle. This is a rewrite, not an integration.

The JAR is not installed by Composer. Historically you download compiler.jar yourself and commit it. In a Laravel context, that means a binary artifact in your repo that nobody documents and that CI must have. This is a classic "works on my machine" trap when the JAR goes missing or a different version is picked up — you get an opaque Java error, not a PHP stack trace.

ADVANCED mode breaks dynamic property access silently. The classic failure: code passes vite build / assetic:dump, deploys, then throws undefined is not a function in production because Closure renamed a property that was accessed via string key or reflection. Tells that you are at risk: obj['key'] access patterns, goog.exportSymbol usage, @export annotations missing, dynamic imports. Always diff behavior in staging, not just build success.

Source maps drift. If you compile to app.min.js without emitting a source map alongside, production stack traces point at minified line 1. Configure --create_source_map and make sure your error reporter resolves it. The JMS bundle offers no help here — it is a compiler wrapper, not a source-map-aware pipeline.

Build-time only, but it can still hurt. The Closure compiler is a build artifact generator, so production scaling is unaffected. CI scaling is a different story: JVM startup plus ADVANCED compilation on a large bundle is slow and memory-hungry, and serializes the pipeline. Cache output keyed on a hash of input files, or CI times will regress noticeably.

Extension points are effectively none. Because the bundle is unmaintained (0 dependents, no issue triage, no CVE path), any customization means forking. If you fork, isolate it behind the JsCompiler interface above so a future swap to the npm compiler is a one-class change, not a hunt through Assetic configuration.

Legacy Symfony2 apps still using it: pin both the bundle and the compiler.jar version explicitly, and add a modernization backlog item ("which apps depend on Closure Tooling, what is the migration cost?"). Do not deepen the dependency with new Soy templates or new compiler flags — that just increases the eventual migration cost.

When in doubt, reach for Vite. The single strongest piece of advice from every angle of review of this package: for ordinary minification and bundling, laravel-vite-plugin already does the job with an actively maintained owner. Only reach for Closure Compiler specifically when ADVANCED-mode dead-code elimination and renaming are genuinely load-bearing — and even then, use the npm package, not this bundle.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console