feat(ViteAssetExtension): Améliore la gestion des assets et ajoute la détection de bot.
```
This commit is contained in:
Serreau Jovann
2026-01-22 22:17:32 +01:00
parent bbf508ff09
commit ccf1c3c042
2 changed files with 82 additions and 52 deletions

View File

@@ -1,34 +1,46 @@
import './reserve.scss';
import * as Sentry from "@sentry/browser";
import {UtmEvent,UtmAccount} from "./tools/UtmEvent.js";
import * as Turbo from "@hotwired/turbo"
// On ne fait plus d'import statique de Sentry ici
import {UtmEvent, UtmAccount} from "./tools/UtmEvent.js";
import * as Turbo from "@hotwired/turbo";
// --- INITIALISATION SENTRY ---
Sentry.init({
dsn: "https://803814be6540031b1c37bf92ba9c0f79@sentry.esy-web.dev/24",
tunnel: "/sentry-tunnel",
integrations: [Sentry.browserTracingIntegration()],
tracesSampleRate: 1.0,
});
// --- INITIALISATION SENTRY (OPTIMISÉE BOT/LIGHTHOUSE) ---
const initSentry = async () => {
// On vérifie si on n'est pas un bot (la variable est passée par le PHP/Twig ou détectée en JS)
const isBot = /bot|googlebot|crawler|spider|robot|crawling|lighthouse/i.test(navigator.userAgent);
if (!isBot) {
try {
// Import dynamique : Le code de Sentry n'est téléchargé QUE si on entre ici
const Sentry = await import("@sentry/browser");
Sentry.init({
dsn: "https://803814be6540031b1c37bf92ba9c0f79@sentry.esy-web.dev/24",
tunnel: "/sentry-tunnel",
integrations: [Sentry.browserTracingIntegration()],
tracesSampleRate: 1.0,
});
} catch (e) {
}
}
};
// --- LOGIQUE DU REDIRECT ---
const initAutoRedirect = () => {
const container = document.getElementById('payment-check-container');
if (container && container.dataset.autoRedirect) {
const url = container.dataset.autoRedirect;
// On attend 5 secondes avant de rediriger via Turbo
setTimeout(() => {
// On vérifie que l'utilisateur est toujours sur la page de check
if (document.getElementById('payment-check-container')) {
Turbo.visit(url);
}
}, 10000);
}
}
// --- LOGIQUE DU LOADER TURBO ---
const initLoader = () => {
let loaderEl = document.getElementById('turbo-loader');
if (!loaderEl) {
loaderEl = document.createElement('div');
loaderEl.id = 'turbo-loader';
@@ -54,7 +66,6 @@ const initLoader = () => {
}, 300);
};
// --- ÉVÉNEMENTS TURBO (SANS BOUCLE INFINIE) ---
document.addEventListener("turbo:click", showLoader);
document.addEventListener("turbo:submit-start", showLoader);
document.addEventListener("turbo:load", hideLoader);
@@ -87,7 +98,6 @@ const initCatalogueSearch = () => {
const category = btn.getAttribute('data-filter').toLowerCase();
let count = 0;
// UI boutons
filters.forEach(f => {
f.classList.remove('bg-slate-900', 'text-white');
f.classList.add('bg-white', 'text-slate-500');
@@ -95,7 +105,6 @@ const initCatalogueSearch = () => {
btn.classList.add('bg-slate-900', 'text-white');
btn.classList.remove('bg-white', 'text-slate-500');
// Filtrage
products.forEach(item => {
const itemCat = item.getAttribute('data-category').toLowerCase();
if (category === 'all' || itemCat.includes(category)) {
@@ -115,13 +124,14 @@ const initCatalogueSearch = () => {
// --- INITIALISATION ---
document.addEventListener('DOMContentLoaded', () => {
initSentry(); // Appel asynchrone de Sentry
initLoader();
initMobileMenu();
initCatalogueSearch();
initAutoRedirect();
customElements.define('utm-event',UtmEvent)
customElements.define('utm-account',UtmAccount)
if (!customElements.get('utm-event')) customElements.define('utm-event', UtmEvent);
if (!customElements.get('utm-account')) customElements.define('utm-account', UtmAccount);
});
document.addEventListener('turbo:load', () => {
@@ -130,7 +140,6 @@ document.addEventListener('turbo:load', () => {
initAutoRedirect();
});
// Nettoyage avant cache pour éviter les bugs au retour arrière
document.addEventListener("turbo:before-cache", () => {
document.querySelectorAll('.product-item').forEach(i => i.style.display = 'block');
if (document.getElementById('empty-msg')) document.getElementById('empty-msg').classList.add('hidden');

View File

@@ -1,16 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Twig;
use Detection\MobileDetect;
use Jaybizzle\CrawlerDetect\CrawlerDetect;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Nelmio\SecurityBundle\EventListener\ContentSecurityPolicyListener;
class ViteAssetExtension extends AbstractExtension
{
const CACHE_KEY = 'vite_manifest';
private const CACHE_KEY = 'vite_manifest';
private ?array $manifestData = null;
private readonly bool $isDev;
@@ -18,8 +22,9 @@ class ViteAssetExtension extends AbstractExtension
private readonly string $manifest,
private readonly CacheItemPoolInterface $cache,
private readonly ContentSecurityPolicyListener $cspListener,
private readonly RequestStack $requestStack,
) {
$this->isDev = $_ENV['VITE_LOAD'] === "0";
$this->isDev = ($_ENV['VITE_LOAD'] ?? "1") === "0";
}
public function getFunctions(): array
@@ -27,25 +32,29 @@ class ViteAssetExtension extends AbstractExtension
return [
new TwigFunction('vite_asset', $this->asset(...), ['is_safe' => ['html']]),
new TwigFunction('isMobile', $this->isMobile(...), ['is_safe' => ['html']]),
new TwigFunction('vite_favicons', $this->favicons(...), ['is_safe' => ['html']])
];
}
/**
* Récupère le nonce pour les scripts via le Listener de Nelmio
*/
private function getNonce(): string
{
// Dans la v3.8, on utilise getNonce('script') sur le listener
return $this->cspListener->getNonce('script');
}
public function isMobile(): bool
{
$detect = new MobileDetect();
return $detect->isMobile() || $detect->isTablet();
}
private function isBot(): bool
{
$request = $this->requestStack->getCurrentRequest();
if (!$request) return false;
$crawlerDetect = new CrawlerDetect($request->headers->all());
return $crawlerDetect->isCrawler();
}
private function getNonce(): string
{
return $this->cspListener->getNonce('script');
}
private function loadManifest(): void
{
if ($this->manifestData === null) {
@@ -82,35 +91,47 @@ HTML;
{
$this->loadManifest();
$nonce = $this->getNonce();
$isBot = $this->isBot();
$file = $this->manifestData[$entry]['file'] ?? '';
$css = $this->manifestData[$entry]['css'] ?? [];
$entryData = $this->manifestData[$entry] ?? null;
if (!$entryData) return '';
$html = <<<HTML
$file = $entryData['file'];
$css = $entryData['css'] ?? [];
$imports = $entryData['imports'] ?? []; // Les fichiers JS secondaires (chunks)
$html = '';
// 1. On charge le fichier JS principal (ex: reserve.js)
$html .= <<<HTML
<script type="module" src="/build/{$file}" crossorigin="anonymous" nonce="{$nonce}" defer></script>
HTML;
// 2. On traite les imports (chunks comme Sentry BrowserTracing)
foreach ($imports as $importKey) {
$importData = $this->manifestData[$importKey] ?? null;
if (!$importData) continue;
$importFile = $importData['file'];
// LOGIQUE DE FILTRAGE :
// Si c'est un bot ET que le fichier contient "browserTracing" dans son nom d'origine
if ($isBot && str_contains($importKey, 'browserTracingIntegration')) {
$html .= "";
continue;
}
// Sinon on l'ajoute normalement
$html .= <<<HTML
<link rel="modulepreload" href="/build/{$importFile}" nonce="{$nonce}">
HTML;
}
// 3. On ajoute le CSS
foreach ($css as $cssFile) {
$html .= '<link rel="stylesheet" href="/build/'.$cssFile.'" crossorigin="anonymous"/>';
}
return $html;
}
public function favicons(): string
{
return $this->isDev ? '<link rel="icon" href="/favicon.ico">' : $this->faviconsProd();
}
private function faviconsProd(): string
{
$this->loadManifest();
$faviconHtml = "";
foreach ($this->manifestData as $key => $favicon) {
if(!str_contains($key, ".js") && isset($favicon['file'])) {
$faviconHtml .= '<link rel="icon" href="/build/'.$favicon['file'].'" type="image/x-icon">';
}
}
return $faviconHtml;
}
}