# L4.2 — Vendorização de assets externos + preconnect/defer (single source of truth)

**Objetivo (P2):** eliminar sub-recursos de terceiros (CDN/fontes) do critical path, tornando o
projeto a **única fonte da verdade** dos assets (mesma origem → gzip server-side + herança do
`immutable` de L0.3) e aplicar `defer`/`async` onde não há dependente inline síncrono.

Seed: `c50e` (L4.2 Bundling / reduzir requests) — adotada no plano `pl-cc49`.

## Contexto / problema encontrado

- ~9 domínios externos serviam sub-recursos de bloco em views: `cdnjs.cloudflare.com`,
  `fonts.googleapis.com`, `maxcdn.bootstrapcdn.com` (legado), `ajax.googleapis.com`,
  `pro.fontawesome.com`. Cada request cross-origin adicionava handshake (DNS/TCP/TLS) e dependia
  de disponibilidade/performance de 3rd-party.
- sniffCSS no canário (`https://infra-update.devs.maxima.inf.br`) confirmou que a home renderiza
  `font-family: Rubik, sans-serif` (header/conteúdo) e `MR` (body) → as Google Fonts
  (Rubik/Nunito Sans) **são realmente aplicadas**; logo vendorizar valia a pena (não era CSS morto).
- **Zero** `defer`/`async` em views; scripts pesados (jQuery, Sortable, charts) estavam no fim do
  `<body>` (não render-blocking), então o ganho real veio da remoção de CSS externo bloqueante
  (fontes/FA/angular-material/BS).
- **Bug pré-existente:** recaptcha usava o placeholder literal `reCAPTCHA_site_key` em
  `api.js?render=…` → recaptcha não funcionava em produção (chave inválida rejeitada pelo Google).

## O que foi feito

1. **Vendorização → `public/assets/vendor/` (única fonte da verdade)**
   - Baixados e versionados localmente: Google Fonts (Rubik, Nunito Sans, Source Sans Pro,
     Encode Sans Expanded, Lato → `fonts/google/` com `fonts.css` + **73 `woff2`**; URLs
     reescritas para `woff2/…`), `popper.min.js` (unificado ao copy local já usado em
     modelo1/2/6), Font Awesome 5.15.4 (`fontawesome/css/all.min.css` + `webfonts/`),
     Sortable 1.15.0, angular-material 1.0.0, Material Icons (1 `woff2`), Bootstrap 3.3.6 +
     glyphicons (admin forms), select2 4.0.3, Chart.js 2.7.0.
   - Todas as views trocadas de CDN → `{{ asset('assets/vendor/...') }}`: `default/master`,
     `modelo1…6/head`, `newsbit/head`, `vendor/adminlte/master`, componentes (`inputs/icon`,
     `inputs/archive`), forms de conteúdo (transparência-fiscal, rede-social, atas-e-pontos,
     publicações, acesso-rapido), `changelog` (form/index), `acesso-rapido/form`.
   - Removido `@import` Lato morto (`newsbit/transparencia-fiscal/index`, `modelo3/4 acesso-rapido`).
     CSS de fontes mantém `font-display: swap` (139 `@font-face`) → sem FOIT.

2. **`preconnect` para 3rd-parties remanescentes (web_performance §HTML)**
   - Partial `portal/layouts/partials/preconnect.blade.php` com `gstatic` + `google` + `vlibras`,
     incluído em `default/master` e nos 6 `modelo*/head.blade.php`.
   - `gstatic` inline em `vendor/adminlte/master` (charts loader do dashboard/relatórios).

3. **`defer` no widget vlibras (web_performance §JS)**
   - `portal/components/vlibras/vlibras.blade.php:9` →
     `<script defer src="https://vlibras.gov.br/app/vlibras-plugin.js">` (auto-inicializa, seguro).

4. **Correção do recaptcha (web_security + laravel)**
   - `portal/statics/contact.blade.php:65` e `…/ouvidoria/contact.blade.php:68`:
     `reCAPTCHA_site_key` → `{{ config('recaptcha.public_key') }}` (de `config/recaptcha.php` /
     `env('RECAPTCHA_PUBLIC_KEY')`); mantido `async`.

5. **Externos mantidos (justificáveis, documentados no EPIC L4.2)**
   - `www.gstatic.com/charts/loader.js` (relatório-estatístico + admin dashboard — lazy-carrega o
     resto do gstatic; não vendorizável de forma limpa), `vlibras.gov.br` (widget de acessibilidade
     hospedado), `google.com/recaptcha` (precisa da site-key), `pro.fontawesome.com` FA-Pro
     (licenciado, em `banner-modal`). `swagger`/`welcome` são dev-only.

## Validação

- `grep` de `cdnjs|fonts.googleapis|maxcdn|ajax.googleapis` em `resources/views` (excluindo `{{`
  e FA-Pro) → **0** refs vendorizáveis.
- `reCAPTCHA_site_key` literal → **0** (corrigido).
- Vendored assets sob `FilesMatch` L0.3 (`public/.htaccess:136`) →
  `Cache-Control: public, max-age=31536000, immutable` + gzip/Brotli server-side.
- sniffCSS no canário (pós-deploy do usuário): `font-family` da home **inalterada** (Rubik) e
  **ausência** de requests a `fonts.googleapis`/`cdnjs`/`maxcdn`.
- `curl -I` em `assets/vendor/...` → 200 + `content-encoding: gzip` (ou br) +
  `cache-control: …immutable`.

## Como reconstruir (reproducibilidade)

- Os assets vendorizados foram baixados dos respectivos CDNs oficiais (cdnjs 4.0.3 select2/Chart.js
  2.7.0, Google Fonts CSS2 combinado com `&display=swap`, FA 5.15.4, etc.). Para rebaixar/atualizar:
  refazer o download e reescrever `url()` internas para caminhos relativos sob `assets/vendor/<lib>/`.
- **Bust de cache ao atualizar lib:** a regra L0.3 é `immutable` e os arquivos não têm hash no nome.
  Ao trocar uma lib, **namespacear por versão** (ex.: `assets/vendor/select2/4.0.3/`) — já feito para
  `bootstrap-3.3.6/` e `chart.js` (versão no filename). O `select2/` atual carece de versão no path
  (anotado).
- `select2.css` 4.0.3 não referencia sprite (`url()`), então `select2.png`/`select2x2.png` baixados
  são órfãos inofensivos.

## Arquivos

- `public/assets/vendor/{fonts/google, fontawesome, sortable, angular-material, material-icons, bootstrap-3.3.6, select2, chart.js}/…` (novos)
- `resources/views/portal/layouts/partials/preconnect.blade.php` (novo)
- `resources/views/portal/layouts/{default/master, modelo1…6/head}.blade.php`
- `resources/views/vendor/adminlte/master.blade.php`
- `resources/views/portal/components/vlibras/vlibras.blade.php`
- `resources/views/portal/statics/contact.blade.php`, `…/ouvidoria/contact.blade.php`
- componentes/forms citados em "O que foi feito"
- `plan/2026-08-09-infra-update.epic.md` (L4.2 reescrito + seção 6: pendências CSP/fontes)

## Notas de deploy

- Implantar no canário `infra-update.devs.maxima.inf.br`; o usuário faz o deploy (sem commit automático).
- Validar recaptcha real (agora com `config('recaptcha.public_key')` — requer `RECAPTCHA_PUBLIC_KEY`
  setado no `.env`/config).
- Medir `requests` externos e `font-display` via Lighthouse/sniffCSS; comparar contra baseline
  `2026-09-08-12-10` com `comparar.py`.

## Rodada 2 — flags async/defer/preconnect (investigação modelo1‑6)

**Objetivo (P2, derivado):** a partir da investigação read-only por subagentes dos layouts
`modelo1…6`, aplicar melhorias de flags (`preconnect`/`defer`/`loading=lazy`/CSS não-bloqueante)
sem mover posições nem agrupar bundles.

### O que foi feito
1. **preconnect** para origins de share/embed/vídeo ainda externas no partial
   `portal/layouts/partials/preconnect.blade.php` (compartilhado pelos 6 modelos):
   `youtube.com`, `static.addtoany.com`, `www.addtoany.com`, `facebook.com`,
   `queimadas.pb.gov.br` (dns-prefetch). AddThis ignorado (sem loader ativo).
2. **Regressão fechada — DataTables i18n:** vendorizado `Portuguese-Brasil.json` →
   `public/assets/components/DataTables/i18n/` e reescrito em 15 views (`asset(...)`).
3. **defer** em 14 `<script>` locais end-of-body de `modelo6/layout/scripts.blade.php`
   (html5shiv/respond/modernizr mantidos síncronos).
4. **CSS não-crítico não-bloqueante** (`media="print" onload="this.media='all'"`) em
   lightgallery/lightslider/daterangepicker/DataTables (modelo1/2/5/6) e lightgallery
   (modelo3/4). ionicons mantido render-blocking.
5. **loading="lazy"** nos 6 iframes YouTube de `home/modalVideo.blade.php`.

### Não alterado
- `<script>` externo: vlibras já `defer`; `gstatic charts/loader.js` tem init inline síncrono
  (`google.charts.load`) → `defer` quebraria gráficos.
- iframes de sidebar (PHP-concatenados) e via JS — sem `lazy` (risco).

### Arquivos (rodada 2)
- `resources/views/portal/layouts/partials/preconnect.blade.php`
- `public/assets/components/DataTables/i18n/Portuguese-Brasil.json` (novo)
- 15 views `relatorio-estatistico*/js.blade.php`, `licitacoes/index`, `full-report-information`,
  `relatorio-estatistico` (default), `modelo6/relatorio-estatistico*`
- `resources/views/portal/layouts/modelo6/layout/scripts.blade.php`
- `resources/views/portal/layouts/modelo{1,2,3,4,5,6}/layout/head.blade.php`
- `resources/views/portal/layouts/modelo{1,2,3,4,5,6}/home/modalVideo.blade.php`

### Pendências remanescentes
- jQuery/bootstrap duplicados (modelo5, modelo6/ouvidoria) — refatoração, não flag.
- Origens em `incorporacao_head`/`incorporacao_rodape` (DB) — auditar.

## Pendências (registradas no EPIC §6)

- **D — CSP strict** (web_security): adicionar `Content-Security-Policy` com nonce + allowlist
  mínima (`gstatic`, `google`/recaptcha, `vlibras`, `pro.fontawesome`); elimina XSS e isola 3rd-party.
- **E — critical path de fontes/CLS** (web_performance): `preload`+`onload` ou split do `@font-face`
  Rubik; `font-size-adjust: from-font`.
