MovixOpenSource/vite.config.ts
Movix 4bba4cab40 chore: drop firebase + perf wave + mobile v2.5.3 + uqload HLS + multi-receiver cast + SW gateway
Nettoyage massif côté frontend, grosse passe perf, app mobile v2.5.3
(écran qui ne s'éteint plus + fix build), extracteur Uqload HLS sur
extensions/userscript, redirection réseau-bloqué via passerelle
explicative et cast cross-receiver/cross-browser.

Nettoyage Firebase & code mort
- Drop firebase + @firebase/firestore (package.json + vite.config
  optimizeDeps + alias)
- Suppression src/config/firebase.ts (jamais utilisé)
- Suppression services morts : adminService, commentService, logService,
  notificationService + composants liés (AdminLogin, CommentSection,
  CommentItem, ReactionBar) + types/Comment.ts
- Suppression scripts non-utilisés : bundle-report.mjs, publish-app.mjs
- Net : -3 449 lignes

Perf carrousels & TMDB images
- useTmdbImages : 1 seul fetch TMDB pour logo + poster (au lieu de 2),
  prefetch idle au mount du carousel + dedup via inflight map (0 doublon
  entre prefetch et hooks), include_image_language=fr,en,null pour
  récupérer les versions FR-prioritisées
- EmblaCarousel : pré-décode des posters off-DOM (img.decode) en idle
  pour éliminer le coût de décode pendant le scroll horizontal, priority
  cap statique (eager + fetchpriority=high pour les N premières cards
  selon viewport), suppression du tracking visibleSlides (state churn)
- content-visibility:auto sur slides hors viewport (skip layout/paint
  natif), exclu des slides Top 10 pour ne pas clipper le digit débordant
- will-change:transform pré-promotion compositing layer (PC only via
  hover:hover)
- Embla duration 25 → 15 (snap plus rapide, moins de frames composées)
- Boutons nav carousel : suppression backdrop-blur (cher), bg opaque
- LazyImage : width/height attrs pour éviter CLS + alléger placeholder

Top progress bar (style YouTube)
- TopProgressBar : asymptote vers 85% pendant chunk loads, fill 100%
  smooth + fade out à la fin, 120ms grace period pour suppress flash
  sur chunks cachés
- lazyWithRetry tracke les loads actifs + dispatch chunk:load:start /
  chunk:load:end events sur window
- Routes loaders supportent { silent: true } pour skip le tracking sur
  les prefetch (PrefetchLink hover + IDLE_PREFETCH au mount)

SW redirect → passerelle movix.health explicative
- buildMirrorUrl() factorise la construction d'URL miroir avec contexte
- handleNavigation passe ?from=&reason=unreachable&error=&via= à la
  gateway pour qu'elle affiche une popup explicative au user au lieu
  d'un redirect silencieux
- MOVIX_FORCE_REDIRECT message handler passe reason=api-errors
- blockDetection.ts transmet la dernière erreur axios au SW
- index.html : cache-bust du Cast SDK URL (cb=Date.now()) pour éviter
  qu'un SDK stale ne reste coincé en cache navigateur/SW

Cast (Chromecast + AirPlay + Remote Playback API)
- loadMediaOnCastWithFallback : retry sur 3 MIME HLS différents
  (application/vnd.apple.mpegurl, x-mpegurl, x-mpegURL) — certains
  receivers rejettent le premier choix selon firmware
- Subtitles externes attachés au cast (CastSubtitleTrack) via
  chrome.cast.media.Track + TextTrackStyle par défaut (50% black
  background, edge outline) lisible sur la majorité des TV
- isRemotePlaybackSupported : fallback W3C Remote Playback API pour
  Firefox / Edge avec cast extension bloquée / Brave avec shields →
  device discovery + picker via video.remote.prompt()
- initializeAirPlay supporte désormais les deux paths (WebKit AirPlay
  pour Safari + Remote Playback pour les autres)
- Détection castSdkBlocked : si le SDK Cast ne charge pas en 5s
  (adblock, FAI, shield), affiche un message d'erreur explicite au
  lieu du générique "aucun device trouvé"
- 7 nouvelles strings i18n FR + EN pour les états cast :
  castUnavailable, castUnavailableNoDevices,
  castUnavailableUnsupportedBrowser, castUnavailableSdkBlocked,
  castUnavailableHelpChromecast, castUnavailableHelpAirPlay,
  castUnavailableSeeHelp

App mobile Android v2.5.3 (versionCode 12)
- AndroidManifest : android:keepScreenOn="true" sur MainActivity →
  l'écran ne s'éteint plus pendant le visionnage (avant le user devait
  toucher l'écran toutes les 30s pour pas que le téléphone se lock en
  plein film)
- build.gradle : exclude androidx.legacy:legacy-support-core-utils
  (tiré transitivement par mediarouter → palette) qui embarquait ses
  propres androidx.autofill.R$attr → "duplicate class" au mergeDexRelease
  contre le module androidx.autofill:autofill:1.1.0 d'appcompat
- gradle.properties : android.nonTransitiveRClass=true pour empêcher
  chaque lib de regénérer ses R classes transitivement (même cause
  racine du duplicate class)
- version.json bumped 2.5.2 → 2.5.3, nouvel APK (-17KB), release notes
  FR : "Correction extraction du lecteur Uqload" + "L'écran ne s'éteint
  plus lors de l'utilisation de l'app"
- apkUrl repointé sur le repo Movix-STMG (au lieu de movixcorp)

Userscript + Extensions Chrome/Firefox — Uqload HLS
- extractUqload : préfère désormais le master.m3u8 multi-bitrate (qualité
  adaptative) au v.mp4 single-quality. Regex tente d'abord
  /master\.m3u8/, sinon n'importe quel .m3u8, et fallback sur l'ancien
  pattern v.mp4 si rien trouvé. Les 3 cibles (Chrome MV3, Firefox MV2,
  Tampermonkey) sont synchronisées sur la même logique.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:28:51 +02:00

129 lines
4.1 KiB
TypeScript

import { defineConfig, type Plugin } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
import { readFileSync, writeFileSync, existsSync } from 'fs'
import { visualizer } from 'rollup-plugin-visualizer'
const buildId = process.env.CF_PAGES_COMMIT_SHA || process.env.COMMIT_REF || new Date().toISOString()
process.env.VITE_APP_BUILD_ID = buildId
function injectSwConfig(): Plugin {
const replacePlaceholders = (source: string): string => {
const mirrors = (process.env.VITE_DEFAULT_MIRRORS || 'movix.health')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
const configUrl =
process.env.VITE_MIRRORS_CONFIG_URL || 'https://rentry.co/movix'
return source
.replace(/__MOVIX_DEFAULT_MIRRORS__/g, JSON.stringify(mirrors))
.replace(/__MOVIX_CONFIG_URL__/g, JSON.stringify(configUrl))
}
return {
name: 'movix-sw-inject',
// Mode build : transforme dist/sw.js après que Vite ait copié public/sw.js
closeBundle() {
const swPath = resolve(__dirname, 'dist/sw.js')
if (!existsSync(swPath)) return
const contents = readFileSync(swPath, 'utf-8')
writeFileSync(swPath, replacePlaceholders(contents), 'utf-8')
},
// Mode dev : intercepte GET /sw.js et sert une version transformée
configureServer(server) {
server.middlewares.use((req, res, next) => {
const pathOnly = (req.url || '').split('?')[0]
if (pathOnly !== '/sw.js') {
return next()
}
const swPath = resolve(__dirname, 'public/sw.js')
if (!existsSync(swPath)) return next()
const contents = readFileSync(swPath, 'utf-8')
res.setHeader('Content-Type', 'application/javascript; charset=utf-8')
res.setHeader('Cache-Control', 'no-store')
res.end(replacePlaceholders(contents))
})
},
}
}
export default defineConfig({
logLevel: 'warn',
plugins: [
react(),
injectSwConfig(),
...(process.env.ANALYZE === 'true'
? [
visualizer({
filename: 'dist/stats.html',
gzipSize: true,
brotliSize: true,
template: 'treemap',
open: false,
}),
]
: []),
],
server: {
host: true,
port: 3000,
hmr: true,
watch: {
usePolling: true,
interval: 100,
ignored: ['**/node_modules/**', '**/.git/**'],
},
},
preview: {
host: true,
port: 3000,
},
build: {
target: 'es2020', // explicit, was implicit es2020 in Vite 5
chunkSizeWarningLimit: 600, // uncompressed kB; warning only, doesn't fail
reportCompressedSize: false, // skip per-chunk gzip/brotli computation (slow + verbose)
commonjsOptions: {
include: [/node_modules/],
sourceMap: false,
transformMixedEsModules: true
},
rollupOptions: {
input: './index.html',
// Silence per-occurrence noise from minified CJS-in-ESM bundles (dashjs floods 2 MiB+)
onLog(level, log, defaultHandler) {
if (log.code === 'COMMONJS_VARIABLE_IN_ESM') return
defaultHandler(level, log)
},
output: {
codeSplitting: {
groups: [
{
name: 'react-vendor',
test: /[\\/]node_modules[\\/](react|react-dom|react-router-dom|react-helmet-async|scheduler|uuid)[\\/]/,
},
{
name: 'radix',
test: /[\\/]node_modules[\\/](@radix-ui|@headlessui|class-variance-authority|tailwind-merge|clsx|tailwindcss-animate)[\\/]/,
},
{
name: 'motion',
test: /[\\/]node_modules[\\/](framer-motion|lenis|sonner)[\\/]/,
},
{
name: 'i18n',
test: /[\\/]node_modules[\\/](i18next|i18next-browser-languagedetector|react-i18next)[\\/]/,
},
{
name: 'markdown',
test: /[\\/]node_modules[\\/](react-markdown|remark-emoji|remark-gfm)[\\/]/,
},
],
},
},
}
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
}
}
})