# L6.1 — Minificação de HTML (middleware de resposta)

**Camada:** L6 (Compressão de HTML / agressiva) · **Critérios A1:** `ttfb`, `pagina`, `download`/`parse`
**Stack:** Laravel 5.5.50 / PHP 7.2.34 · **Risco:** baixo · **Esforço:** baixo

> Documentação dedicada da feature de minificação de HTML. Para o digestor de imagens WebP,
> ver `webp-middleware-l5.md`.

---

## 1. Objetivo

Reduzir o peso do HTML servido colapsando espaços em branco entre tags. O HTML médio do portal mede
**~135 KB**; a minificação ataca `pagina`/`ttfb` diretamente (menos bytes para baixar e fazer parse).

Restrições de projeto respeitadas:
- **Não** mexer no conteúdo de blocos sensíveis (`<script>`, `<style>`, `<pre>`, etc.).
- **Não** remover comentários HTML `<!-- -->` (decisão: zero risco).
- **Não** usar regex no middleware para converter comentários de JS (ver §3).

---

## 2. Implementação

### 2.1 Middleware — `app/Http/Middleware/HtmlMinify.php`

```php
declare(strict_types=1);

namespace App\Http\Middleware;

use Closure;
use Symfony\Component\HttpFoundation\Response;

class HtmlMinify
{
    protected $preserveTags = [
        'pre', 'code', 'textarea', 'script', 'style', 'svg',
    ];

    public function handle($request, Closure $next)
    {
        $response = $next($request);

        if (
            !$response instanceof Response
            || $request->method() !== 'GET'
            || config('app.debug') === true
            || strpos($response->headers->get('Content-Type') ?? '', 'text/html') === false
        ) {
            return $response;
        }

        $content = $response->getContent();
        if (empty($content)) {
            return $response;
        }

        $response->setContent($this->minify($content));
        return $response;
    }

    protected function minify($html)
    {
        $sensitive = [];
        $placeholderIndex = 0;

        // 1) captura blocos sensíveis inteiros num placeholder
        $html = preg_replace_callback(
            '#<(' . implode('|', $this->preserveTags) . ')(\s[^>]*)?>.*?</\1>#is',
            function ($match) use (&$sensitive, &$placeholderIndex) {
                $placeholder = '###HTMLMINIFY_PH_' . ($placeholderIndex++) . '###';
                $sensitive[$placeholder] = $match[0];
                return $placeholder;
            },
            $html
        );

        // 2) colapsa whitespace ENTRE tags
        $lines = explode("\n", $html);
        $cleaned = [];
        foreach ($lines as $line) {
            $trimmed = trim($line);
            if ($trimmed === '') {
                continue;
            }
            $cleaned[] = $trimmed;
        }
        $html = implode("\n", $cleaned);
        $html = preg_replace('/\s{2,}/', ' ', $html);
        $html = str_replace('> <', '><', $html);

        // 3) restaura blocos sensíveis (verbatim)
        $html = str_replace(
            array_keys($sensitive),
            array_values($sensitive),
            $html
        );

        return trim($html);
    }
}
```

### 2.2 Registro — `app/Http/Kernel.php`

No grupo `web`, **após `CacheControl` e antes de `PictureTransform`**:

```php
'web' => [
    \App\Http\Middleware\PageCache::class,
    \App\Http\Middleware\CacheControl::class,
    \App\Http\Middleware\HtmlMinify::class,      // novo
    \App\Http\Middleware\PictureTransform::class,
    \App\Http\Middleware\EncryptCookies::class,
    // ...
],
```

**Por quê esta ordem?** O middleware do grupo `web` executa o outbound (resposta) em ordem **inversa**
ao array. Assim `PictureTransform` roda primeiro (gera os `<picture>`), depois `HtmlMinify` minifica o
HTML final (inclusive os `<picture>`), e por fim `CacheControl`/`PageCache`. O resultado é um HTML já
ótimo sendo cacheado.

### 2.3 Guards (quando NÃO minifica)

| Condição | Motivo |
|---|---|
| `method() !== 'GET'` | POST/PUT/etc. não precisam de minificação e podem ter corpo volumoso |
| `config('app.debug') === true` | manter HTML legível em desenvolvimento |
| `Content-Type` ≠ `text/html` | nunca tocar JSON/XML/css/js |

---

## 3. Correção do bug de comentários `//` (sem regex no middleware)

### 3.1 O problema

Minificadores de HTML que colapsam newlines **quebram** comentários de linha `//` de JavaScript: o
`//` "engole" todo o resto do arquivo a partir dali, porque o comentário de linha não tem delimitador
de fim. Ex.:

```html
<script>
  var x = 1; // isto some
  var y = 2; // e isto tambem
</script>
```

Se o newline entre as linhas for removido, `var y = 2; // e isto tambem` vira parte do comentário da
linha anterior → `var y` deixa de existir.

### 3.2 A resolução adotada

**Não** usar regex no middleware para converter comentários (frágil e propenso a falsos positivos em
strings/regex). Em vez disso, converter `// ...` → `/* ... */` **em nível de fonte**, arquivo a
arquivo, via subagentes especializados:

| Escopo | Arquivos | Comentários convertidos |
|---|---|---|
| `resources/views/portal` | 49 | 235 |
| `resources/views/prefecture` | 32 | 74 |
| `resources/views/admin` | 4 | 5 |
| **Total** | **85 blades** | **313** |

Cada subagente recebeu as skills `php-72-correct` e `laravel-55` e a regra estrita de **só** converter
`//` de linha dentro de `<script>` inline (type omitido ou `text/javascript`).

**Preservado intacto (não convertido):**
- `://` — ex.: `https://www.gstatic.com/...`, `//cdn.datatables.net/...` (protocol-relative)
- strings (`'...'`, `"..."`, `` `...` ``)
- regex literais — ex.: `currentUrl.match(/\/diario-oficial\/(\d+)\//)`
- comentários PHP dentro de `@php ... @endphp`
- comentários Blade `{{-- --}}`

### 3.3 Exemplo

```html
<script>
  // isto e um comentario de linha        →   <script>
  var y = 1 + 2; // trailing comment            /* isto e um comentario de linha */
</script>                                          var y = 1 + 2; /* trailing comment */
                                                 </script>
```

Como o conteúdo de `<script>` é preservado integralmente pelo middleware (placeholder), o `//` não
seria sequer tocado em runtime — mas a conversão em fonte elimina qualquer risco residual caso o
bloco deixe de ser preservado (ou em edições futuras), e satisfaz a resolução "explicitar `/* */`".

---

## 4. Arquivos afetados

- `app/Http/Middleware/HtmlMinify.php` — **novo**
- `app/Http/Kernel.php` — registro no grupo `web`
- `resources/views/portal/**` (49), `resources/views/prefecture/**` (32), `resources/views/admin/**` (4)
  — `//` → `/* */` em `<script>` inline

---

## 5. Validação (container `public_infra_update-app-1`)

| Teste | Resultado |
|---|---|
| `php -l` em `HtmlMinify.php` e `Kernel.php` | ✅ sem erros de sintaxe |
| tinker `handle()`: HTML colapsado; `<script>` preservado verbatim (`//`/`https://`/`/* */` intactos) | ✅ |
| Blade compiler em **85 blades alterados** | ✅ compilam sem erro (sem regressão PHP/Blade) |
| Spot-check `contents/diario-oficial/create.blade.php` (regex + template literal) | ✅ `//` internos não tocados |
| `comparar.py` no canário (`pagina`/`ttfb` ↓) | ⏳ pendente de medição |

### 5.1 Caso de teste (tinker)

Entrada:
```html
<p class="x">   texto    com   espacos   </p>
<script>
    // isto e um comentario de linha
    var x = "https://exemplo.com/a/b";  /* bloco existente */
    var y = 1 + 2; // trailing comment
</script>
```

Saída esperada (minificada):
```html
<p class="x"> texto com espacos </p>
<script>
    // isto e um comentario de linha
    var x = "https://exemplo.com/a/b";  /* bloco existente */
    var y = 1 + 2; // trailing comment
</script>
```
→ whitespace entre tags colapsado; `<script>` 100% preservado; `https://` e `/* */` intactos.

---

## 6. Ressalvas / riscos conhecidos

- O `\s{2,}`→`' '` global também normaliza espaços **dentro de valores de atributo** (ex.:
  `value="a  b"` → `value="a b"`). É um efeito cosmético raro; não quebra markup. Se algum caso
  crítico aparecer, restringir o colapso a posições entre `>` e `<`.
- Comentários HTML `<!-- -->` não são removidos (decisão deliberada).
- A medição final de ganho (`comparar.py`) ainda está pendente no canário.

---

## 7. Rastreabilidade

- Issue: `public_infra_update-0662` (L6.1) — **fechada** após implementação.
- EPIC: `plan/2026-08-09-infra-update.epic.md` § L6.1 e § 8.
