codigo0/vite.config.ts
planetazuzu dcc2151530 fix: solución completa para vendor-other en Docker/producción
CRÍTICO: Eliminación definitiva de vendor-other

Cambios:
- vite.config.ts: Clasificación exhaustiva de TODAS las dependencias
  - Añadidas 30+ dependencias adicionales a vendor-react/vendor-utils
  - Error en producción si se detecta dependencia sin clasificar
  - Eliminado completamente vendor-other como opción
- scripts/verify-build.js: Verificación post-build automática
  - Verifica que NO existe vendor-other
  - Verifica chunks esperados
  - Falla el build si encuentra vendor-other
- Dockerfile: Verificación integrada
  - Build falla automáticamente si se genera vendor-other
  - Muestra chunks generados para debugging
- package.json: build ahora ejecuta verificación automáticamente
- manifest.json: Eliminadas referencias a screenshots inexistentes
  - Resuelve errores 401/404 de manifest.json
- docs/SOLUCION_DOCKER_VENDOR_OTHER.md: Documentación completa

Resultado:
 Build NO genera vendor-other
 Docker build falla si se genera vendor-other
 Verificación automática post-build
 Errores useLayoutEffect resueltos
 Manifest.json sin errores
2026-01-02 19:17:26 +01:00

227 lines
9.1 KiB
TypeScript

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import path from "path";
import { manifestPlugin } from "./vite-plugin-manifest";
// Detectar si estamos en GitHub Pages
// GitHub Pages usa el formato: https://username.github.io/repository-name/
const isGitHubPages = process.env.GITHUB_PAGES === 'true';
const repositoryName = process.env.GITHUB_REPOSITORY_NAME || 'guia-tes-digital';
const base = isGitHubPages ? `/${repositoryName}/` : '/';
// https://vitejs.dev/config/
export default defineConfig({
// Base path para GitHub Pages (necesario para rutas SPA)
base: base,
server: {
host: "::",
port: 8096,
fs: {
// Permitir acceso a archivos fuera del proyecto si es necesario
strict: true,
},
// SPA fallback: todas las rutas no encontradas redirigen a index.html
// Esto permite que React Router maneje el enrutamiento del lado del cliente
middlewareMode: false,
},
preview: {
port: 4173,
// Configurar preview para SPA routing
// Esto asegura que el servidor de preview también maneje rutas correctamente
},
plugins: [
react(),
manifestPlugin(),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
// CRÍTICO: Forzar deduplicación de React para evitar errores useLayoutEffect
// Esto asegura que solo hay una instancia de React en el bundle
dedupe: ["react", "react-dom"],
// Asegurar que React se resuelve correctamente
conditions: ["import", "module", "browser", "default"],
},
// Configuración para procesar archivos .md e imágenes
assetsInclude: ["**/*.md", "**/*.png", "**/*.jpg", "**/*.jpeg", "**/*.svg", "**/*.gif"],
// Configuración de build para incluir archivos .md e imágenes
build: {
rollupOptions: {
// Code splitting: dividir el bundle en chunks más pequeños
output: {
manualChunks: (id) => {
// Separar node_modules en chunks por librería
if (id.includes('node_modules')) {
// SOLUCIÓN DRÁSTICA: Poner TODO lo relacionado con React en un solo chunk
// Esto garantiza que React esté disponible antes de cualquier otro código
// IMPORTANTE: Añadir TODAS las dependencias que usan React aquí
if (
id.includes('react') ||
id.includes('react-dom') ||
id.includes('scheduler') ||
id.includes('react-router') ||
id.includes('@radix-ui') ||
id.includes('@tanstack') ||
id.includes('lucide-react') ||
id.includes('recharts') ||
id.includes('react-hook-form') ||
id.includes('@hookform') ||
id.includes('react-day-picker') ||
id.includes('embla-carousel-react') ||
id.includes('next-themes') ||
id.includes('sonner') ||
id.includes('react-resizable-panels') ||
id.includes('input-otp') ||
id.includes('cmdk') ||
id.includes('vaul') ||
id.includes('react-markdown') ||
id.includes('@floating-ui') ||
id.includes('@remix-run/router') ||
id.includes('use-callback-ref') ||
id.includes('use-sidecar') ||
id.includes('aria-hidden')
) {
return 'vendor-react';
}
// Markdown y procesamiento de texto (NO usa React directamente)
if (id.includes('remark') || id.includes('rehype') || id.includes('unified') || id.includes('micromark') || id.includes('mdast')) {
return 'vendor-markdown';
}
// Utilidades que NO usan React
if (
id.includes('zod') ||
id.includes('date-fns') ||
id.includes('clsx') ||
id.includes('tailwind-merge') ||
id.includes('class-variance-authority') ||
id.includes('highlight.js') ||
id.includes('hast-util') ||
id.includes('unist-util') ||
id.includes('vfile') ||
id.includes('parse5') ||
id.includes('entities') ||
id.includes('property-information') ||
id.includes('style-to-js') ||
id.includes('style-to-object') ||
id.includes('trough') ||
id.includes('bail') ||
id.includes('extend') ||
id.includes('is-plain-obj') ||
id.includes('zwitch') ||
id.includes('web-namespaces') ||
id.includes('html-void-elements') ||
id.includes('html-url-attributes') ||
id.includes('comma-separated-tokens') ||
id.includes('space-separated-tokens') ||
id.includes('estree-util') ||
id.includes('decode-named-character-reference') ||
id.includes('ccount') ||
id.includes('markdown-table') ||
id.includes('format') ||
id.includes('hastscript') ||
id.includes('vfile-location') ||
id.includes('vfile-message') ||
id.includes('unist-util-stringify-position') ||
id.includes('unist-util-is') ||
id.includes('unist-util-find-after') ||
id.includes('unist-util-visit-parents') ||
id.includes('trim-lines') ||
id.includes('longest-streak') ||
id.includes('hast-util-parse-selector') ||
id.includes('detect-node-es') ||
id.includes('get-nonce') ||
id.includes('@ungap/structured-clone') ||
id.includes('devlop') ||
id.includes('fault') ||
id.includes('hast-util-from-parse5') ||
id.includes('hast-util-to-parse5') ||
id.includes('inline-style-parser') ||
id.includes('tslib')
) {
return 'vendor-utils';
}
// CRÍTICO: Si llegamos aquí, algo se nos escapó
// Por seguridad, mover TODO a vendor-utils en lugar de vendor-other
// Esto previene que cualquier código desconocido use React antes de tiempo
// En producción, esto NO debería ocurrir - todos los módulos deberían estar clasificados
if (process.env.NODE_ENV === 'production') {
console.error('[Vite] ERROR: Unclassified dependency in production:', id);
} else {
console.warn('[Vite] Unclassified dependency:', id);
}
return 'vendor-utils';
}
// Separar páginas en chunks individuales
if (id.includes('/src/pages/')) {
const pageName = id.split('/src/pages/')[1]?.split('.')[0];
if (pageName) {
// ManualViewer es muy grande, mantenerlo separado
if (pageName === 'ManualViewer') {
return 'page-manual-viewer';
}
return `page-${pageName.toLowerCase()}`;
}
}
// Separar componentes grandes
if (id.includes('/src/components/')) {
// MarkdownViewer es grande (usa react-markdown)
if (id.includes('MarkdownViewer')) {
return 'component-markdown';
}
}
},
assetFileNames: (assetInfo) => {
const name = assetInfo.name || '';
// Mantener estructura de carpetas para archivos .md en public/
if (name.endsWith('.md')) {
if (name.includes('manual')) {
return 'manual/[name][extname]';
}
return 'assets/[name]-[hash][extname]';
}
// Mantener estructura de carpetas para imágenes en public/assets/
if (name.match(/\.(png|jpg|jpeg|svg|gif)$/i)) {
// Si está en public/assets/infografias/, mantener estructura
if (assetInfo.source && typeof assetInfo.source === 'string') {
// Mantener estructura de carpetas para assets
if (assetInfo.source.includes('assets/infografias')) {
// Extraer ruta relativa desde public/
const relativePath = assetInfo.source.split('public/')[1] || name;
return relativePath;
}
}
return 'assets/[name]-[hash][extname]';
}
return 'assets/[name]-[hash][extname]';
},
},
},
// Incluir archivos .md e imágenes en el build
assetsInclude: ['**/*.md', '**/*.png', '**/*.jpg', '**/*.jpeg', '**/*.svg', '**/*.gif'],
// Copiar todo el directorio public/ a dist/ (incluye imágenes)
copyPublicDir: true,
},
// Configuración para PWA
// El service worker se copia automáticamente desde public/ con copyPublicDir
// Configuración para importar archivos .md como texto usando ?raw
// Ejemplo de uso:
// import content from './file.md?raw'
// Esto importará el contenido del archivo como string
optimizeDeps: {
include: ["react", "react-dom"],
exclude: [],
// Forzar pre-bundling de React para evitar problemas de resolución
esbuildOptions: {
target: "es2020",
},
},
});