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

Htmlcompressor Bundle Laravel Package

matthecat/htmlcompressor-bundle

Symfony bundle that automatically minifies HTML responses, stripping unnecessary whitespace and comments to reduce page size and speed up delivery. Simple to install and configure, it helps improve front-end performance without changing templates or application code.

View on GitHub
Deep Wiki
Context7

Getting Started

This is an archived Symfony 2-era bundle that hooks into kernel.response and minifies HTML/XML responses only when the response is cacheable. Treat it as a small, single-purpose listener rather than a framework you configure heavily.

  1. Install and enable (Symfony 2.x style). The package is unmaintained and its Composer constraints target old Symfony/PHP versions; expect conflicts on modern stacks.

    // app/AppKernel.php
    public function registerBundles()
    {
        $bundles = [
            // ...
            new MatTheCat\HtmlCompressorBundle\MatTheCatHtmlCompressorBundle(),
        ];
    }
    
  2. Understand the default behavior before writing config. The bundle's main job is a response listener that checks $response->isCacheable(), inspects the Content-Type for HTML/XML, and rewrites the body with the htmlcompressor port. If you do nothing else, this is what runs.

  3. Look first at these files in the repo:

    • EventListener/ or Listener/ — the kernel.response listener; this is the whole bundle in practice.
    • DependencyInjection/Configuration.php — the real list of supported options (it is small).
    • Resources/config/services.xml (or .yml) — service IDs and listener priority.
  4. First use case: a cached HTML page.

    public function showAction($id)
    {
        $response = $this->render('post/show.html.twig', ['post' => $post]);
    
        // Make the response cacheable so the bundle compresses it
        $response->setPublic();
        $response->setSharedMaxAge(600);
    
        return $response;
    }
    

    Then verify with:

    curl -sI https://example.test/post/1
    curl -s  https://example.test/post/1 | head -c 500
    

    You should see the cache headers and a body with collapsed inter-tag whitespace/comments removed (depending on configuration).

The critical first mental model: this bundle does not minify every response — it minifies responses you have already marked as publicly cacheable.

Implementation Patterns

Opt in via cache headers, not via bundle configuration. The normal workflow is to decide per controller which responses deserve compression by making them shared-cacheable:

$response->setPublic();
$response->setSharedMaxAge(3600);
// or
$response->headers->addCacheControlDirective('public');
$response->headers->addCacheControlDirective('max-age', 3600);

Private, no-store, non-200, and uncacheable responses pass through untouched. This is intentional: you avoid CPU work and corruption risk on user-specific pages.

Pair it with Symfony's HttpCache or a reverse proxy. The bundle rewrites the response before it is stored, so the cached copy is the compressed one. With Symfony's HttpCache:

// web/app.php
$kernel = new AppKernel('prod', false);
$kernel = new AppCache($kernel);

With Varnish/nginx proxy_cache, the same ordering applies: compression happens in the backend, caching in front.

Exclude specific responses without disabling the bundle. Since the gate is isCacheable(), the cleanest exclusion is to override cache headers at the end of the request:

$response->setPrivate();
$response->setMaxAge(0);

This is more reliable than relying on Content-Type sniffing or URL patterns.

Order your own kernel.response listeners before the compressor. If another listener rewrites HTML (injects a banner, rewrites links, adds a footer), it must run first. Check the bundle listener's priority and register yours higher:

$this->dispatcher->addListener('kernel.response', [$this, 'onKernelResponse'], 32);

The bundle's listener should be one of the last things touching the body.

Use the underlying compressor directly for non-response output if the bundle exposes it as a service (check debug:container for the service ID). Typical uses: minifying HTML before writing a static file, email templates, or generated XML sitemaps outside the HTTP cycle.

$compressor = $container->get('mat_the_cat_html_compressor.compressor');
$minified = $compressor->compress($html);

If it is not exposed, instantiate the compressor library directly; the listener contains no other magic.

Tune the compressor for your templates. The htmlcompressor family exposes switches such as comment removal, inter-tag space removal, multi-space removal, CSS/JS compression, quote removal, and preserve-pattern regexes. Enable the aggressive options incrementally and keep a preserve pattern list for things like:

// Protect pre/textarea and inline templates
$compressor->setPreservePatterns([
    '/<\s*pre[^>]*>.*?<\s*\/\s*pre\s*>/is',
    '/<\s*textarea[^>]*>.*?<\s*\/\s*textarea\s*>/is',
    '/<\s*script[^>]*type=["\']text\/template["\'][^>]*>.*?<\s*\/\s*script\s*>/is',
]);

Test the listener boundary, not the compressor internals. In functional tests, assert that a cacheable HTML response is minified and a private response is byte-for-byte untouched. In the test environment, responses are usually not cacheable, so you may need to set cache headers explicitly in the test or disable the listener in test config if it interferes with assertions.

Configuration is set-and-forget. The bundle is small enough that the config tree is essentially "enable/disable plus compressor options." Clear the cache after changes:

app/console cache:clear --env=prod

because listener and compressor settings are compiled into the container.

Gotchas and Tips

  • "It does nothing" is almost always isCacheable() === false. Symfony considers a response cacheable only for certain status codes and when Cache-Control says public, max-age, or s-maxage without private/no-store. Debug by dumping headers and checking the response before assuming the listener is broken.
  • Stale Content-Length is a real truncation risk. If the response already has a Content-Length header, rewriting the body can leave a larger value, causing clients or proxies to truncate. Remove it after compression or ensure your setup recalculates it:
    $response->headers->remove('Content-Length');
    
  • ETag/Last-Modified semantics can drift. The compressed body differs from the original used to compute the ETag. For normal 304 flows this is usually harmless, but avoid re-compressing responses whose ETag is used as strong validation elsewhere.
  • Never compress StreamedResponse, BinaryFileResponse, or empty bodies. getContent() can return false for streams; guard the listener or exclude these response types. Binary payloads with an HTML-ish Content-Type are a corruption waiting to happen.
  • Inline JavaScript and CSS are the biggest breakage source. Whitespace removal inside <script> or <style> can alter semantics (especially with automatic semicolon insertion or CSS descendant selectors). Disable JS/CSS compression first, then enable one at a time.
  • Whitespace is semantically significant in more places than you expect: <pre>, <textarea>, inline elements (<span> spacing), CDATA in XML, and old IE conditional comments. Use preserve patterns and test with real templates, not toy HTML.
  • XML is not HTML. Self-closing tags, namespaces, CDATA, and significant whitespace in text nodes make generic minification risky. Restrict XML compression to machine-generated feeds and sitemaps you control.
  • Debug toolbar/profiler interference. In dev, the web debug toolbar injects markup and responses are typically non-cacheable, so compression usually won't run. Don't debug minification behavior in dev; use a prod-like environment.
  • Listener priority conflicts. If a later kernel.response listener replaces the content, the compression is wasted or the body is re-broken. Use debug:event-dispatcher (Symfony 2.6+) or inspect the service definition to confirm ordering.
  • Sub-requests can cause double work. kernel.response fires for sub-requests too. If the listener does not check isMasterRequest(), fragments may be compressed individually and again inside the master response. Usually idempotent, but wasteful and occasionally destructive.
  • Changing compressor options invalidates cached output. If you use a shared cache, old compressed responses remain until expiry. Version your cache keys or purge when you change minification rules.
  • Marginal benefit over gzip/brotli. Most of the byte savings come from transport compression, which your web server or CDN already does. HTML minification adds CPU cost and breakage risk for a small additional gain; measure before adopting.
  • Archived and Symfony 2-only. No maintainer, no dependents, no updates for modern Symfony/PHP. For new projects, prefer a maintained minifier (voku/html-min, wyrihaximus/html-compress), a response listener you own, or compression at the CDN/proxy layer. If you inherit this bundle, isolate it behind your own listener/service so it can be swapped out.
  • Extension point is the listener, not the bundle. Forking or replacing the kernel.response listener service is the cleanest way to add exclusions, per-route rules, or logging; the bundle has little other surface area.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
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