From 2b7e193906a54d602cc04183c7a239d01272777b Mon Sep 17 00:00:00 2001 From: Hanzo_dev <2002samudiojohan@gmail.com> Date: Sun, 22 Feb 2026 16:19:27 -0500 Subject: [PATCH] =?UTF-8?q?perf:=20optimize=20build=20=E2=80=94=20chunk=20?= =?UTF-8?q?splitting,=20remove=20VueDevTools=20from=20prod,=20fix=20fireba?= =?UTF-8?q?se-tools=20to=20devDeps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - vite.config.ts: Remove VueDevTools from production build (dev only) - vite.config.ts: Add manualChunks for firebase, charts, pdf, vue, maps vendors - vite.config.ts: Increase chunkSizeWarningLimit to 700KB for Google Maps - package.json: Move firebase-tools from dependencies to devDependencies - router/index.ts: Add webpackChunkName groups (transport, discover, user, admin, roles) - Clean up build log files (build_debug.txt, build_error*.txt, etc.) Build time improvement: chunks now load on demand per user role --- frontend/package.json | 2 +- frontend/src/router/index.ts | 132 +++++++++++++----------- frontend/vite.config.ts | 189 +++++++++++++++++++++-------------- 3 files changed, 186 insertions(+), 137 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index b9bf800..4f67027 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,7 +18,6 @@ "axios": "^1.13.2", "chart.js": "^4.5.1", "firebase": "^12.9.0", - "firebase-tools": "^15.7.0", "html2canvas": "^1.4.1", "jspdf": "^4.1.0", "pinia": "^3.0.4", @@ -33,6 +32,7 @@ "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.8.1", "autoprefixer": "^10.4.24", + "firebase-tools": "^15.7.0", "postcss": "^8.5.6", "tailwindcss": "^4.2.0", "typescript": "~5.9.3", diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index ee57f45..dd3f93b 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -4,131 +4,142 @@ import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ + // ─── Vistas Públicas Core (cargadas en el chunk principal) ─────────── { path: '/', name: 'splash', - component: () => import('@/views/SplashScreen.vue'), + component: () => import(/* webpackChunkName: "splash" */ '@/views/SplashScreen.vue'), }, { path: '/map', name: 'map', - component: () => import('@/views/MapView.vue'), - }, - { - path: '/discover', - name: 'discover', - component: () => import('@/views/DiscoverView.vue'), - }, - { - path: '/business/:id', - name: 'business-details', - component: () => import('@/views/BusinessDetailsView.vue'), - }, - { - path: '/routes', - name: 'routes', - component: () => import('@/views/RoutesView.vue'), - }, - { - path: '/schedules', - name: 'schedules', - component: () => import('@/views/SchedulesView.vue'), - }, - { - path: '/coupons', - name: 'coupons', - component: () => import('@/views/CouponsView.vue'), - }, - { - path: '/favorites', - name: 'favorites', - component: () => import('@/views/FavoritesView.vue'), - }, - { - path: '/profile', - name: 'profile', - component: () => import('@/views/ProfileView.vue'), - meta: { requiresAuth: true } - }, - { - path: '/taxi', - name: 'taxi', - component: () => import('@/views/TaxiView.vue'), - }, - { - path: '/bus-stop/:id', - name: 'bus-stop-details', - component: () => import('@/views/BusStopDetailsView.vue'), + component: () => import(/* webpackChunkName: "map" */ '@/views/MapView.vue'), }, { path: '/login', name: 'auth', - component: () => import('@/views/AuthView.vue'), + component: () => import(/* webpackChunkName: "auth" */ '@/views/AuthView.vue'), }, + + // ─── Vistas de Transporte (chunk: transport) ───────────────────────── + { + path: '/routes', + name: 'routes', + component: () => import(/* webpackChunkName: "transport" */ '@/views/RoutesView.vue'), + }, + { + path: '/schedules', + name: 'schedules', + component: () => import(/* webpackChunkName: "transport" */ '@/views/SchedulesView.vue'), + }, + { + path: '/bus-stop/:id', + name: 'bus-stop-details', + component: () => import(/* webpackChunkName: "transport" */ '@/views/BusStopDetailsView.vue'), + }, + { + path: '/taxi', + name: 'taxi', + component: () => import(/* webpackChunkName: "transport" */ '@/views/TaxiView.vue'), + }, + + // ─── Vistas de Descubrir (chunk: discover) ─────────────────────────── + { + path: '/discover', + name: 'discover', + component: () => import(/* webpackChunkName: "discover" */ '@/views/DiscoverView.vue'), + }, + { + path: '/business/:id', + name: 'business-details', + component: () => import(/* webpackChunkName: "discover" */ '@/views/BusinessDetailsView.vue'), + }, + { + path: '/coupons', + name: 'coupons', + component: () => import(/* webpackChunkName: "discover" */ '@/views/CouponsView.vue'), + }, + + // ─── Vistas de Usuario (chunk: user) ───────────────────────────────── + { + path: '/favorites', + name: 'favorites', + component: () => import(/* webpackChunkName: "user" */ '@/views/FavoritesView.vue'), + }, + { + path: '/profile', + name: 'profile', + component: () => import(/* webpackChunkName: "user" */ '@/views/ProfileView.vue'), + meta: { requiresAuth: true } + }, + + // ─── Vistas de Admin (chunk: admin — solo carga para admins) ───────── { path: '/admin', name: 'admin-panel', - component: () => import('@/views/AdminPanel.vue'), + component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminPanel.vue'), meta: { requiresAuth: true, role: 'admin' } }, { path: '/admin/bus-stops', name: 'admin-bus-stops', - component: () => import('@/views/AdminBusStops.vue'), + component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminBusStops.vue'), meta: { requiresAuth: true, role: 'admin' } }, { path: '/admin/routes', name: 'admin-routes', - component: () => import('@/views/AdminRoutes.vue'), + component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminRoutes.vue'), meta: { requiresAuth: true, role: 'admin' } }, { path: '/admin/reports', name: 'admin-reports', - component: () => import('@/views/AdminReports.vue'), + component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminReports.vue'), meta: { requiresAuth: true, role: 'admin' } }, { path: '/admin/schedules', name: 'admin-schedules', - component: () => import('@/views/AdminSchedules.vue'), + component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminSchedules.vue'), meta: { requiresAuth: true, role: 'admin' } }, { path: '/admin/drivers', name: 'admin-drivers', - component: () => import('@/views/AdminDrivers.vue'), + component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminDrivers.vue'), meta: { requiresAuth: true, role: 'admin' } }, { path: '/admin/analytics', name: 'admin-analytics', - component: () => import('@/views/StrategicAnalytics.vue'), + component: () => import(/* webpackChunkName: "admin" */ '@/views/StrategicAnalytics.vue'), meta: { requiresAuth: true, role: 'admin' } }, { path: '/admin/taxis', name: 'admin-taxis', - component: () => import('@/views/AdminTaxis.vue'), + component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminTaxis.vue'), meta: { requiresAuth: true, role: 'admin' } }, { path: '/admin/shuttles', name: 'admin-shuttles', - component: () => import('@/views/AdminShuttles.vue'), + component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminShuttles.vue'), meta: { requiresAuth: true, role: 'admin' } }, + + // ─── Vistas de Roles Especiales (chunk: roles) ─────────────────────── { path: '/promoter', name: 'promoter-dashboard', - component: () => import('@/views/PromoterDashboard.vue'), + component: () => import(/* webpackChunkName: "roles" */ '@/views/PromoterDashboard.vue'), meta: { requiresAuth: true, role: ['PROMOTER', 'ADMIN'] } }, { path: '/driver', name: 'driver-dashboard', - component: () => import('@/views/DriverDashboard.vue'), + component: () => import(/* webpackChunkName: "roles" */ '@/views/DriverDashboard.vue'), meta: { requiresAuth: true, role: ['DRIVER', 'ADMIN'] } }, ], @@ -158,4 +169,3 @@ router.beforeEach((to, _from, next) => { }) export default router - diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index d31aa60..f7bd97c 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,89 +1,128 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' -import VueDevTools from 'vite-plugin-vue-devtools' import { VitePWA } from 'vite-plugin-pwa' import tailwindcss from '@tailwindcss/vite' import path from 'path' // https://vite.dev/config/ -export default defineConfig({ - plugins: [ - vue(), - tailwindcss(), - VueDevTools(), - VitePWA({ - registerType: 'autoUpdate', - includeAssets: ['icon-192.png', 'icon-512.png', 'icon-1024.png', 'favicon.ico'], - manifest: { - name: 'SIBU - Sistema de Transporte', - short_name: 'SIBU', - description: 'Sistema de Transporte Público', - theme_color: '#fee715', - background_color: '#ffffff', - display: 'standalone', - orientation: 'portrait', - scope: '/', - start_url: '/', - icons: [ - { - src: 'icon-192.png', - sizes: '192x192', - type: 'image/png', - }, - { - src: 'icon-512.png', - sizes: '512x512', - type: 'image/png', - }, - { - src: 'icon-1024.png', - sizes: '1024x1024', - type: 'image/png', - }, - ], - }, - workbox: { - globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'], - runtimeCaching: [ - { - urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i, - handler: 'CacheFirst', - options: { - cacheName: 'google-fonts-cache', - expiration: { - maxEntries: 10, - maxAgeSeconds: 60 * 60 * 24 * 365, // 1 year - }, - cacheableResponse: { - statuses: [0, 200], +export default defineConfig(({ mode }) => { + const isDev = mode === 'development' + + return { + plugins: [ + vue(), + tailwindcss(), + // VueDevTools SOLO en desarrollo — no se incluye en el bundle de producción + ...(isDev ? [require('vite-plugin-vue-devtools').default()] : []), + VitePWA({ + registerType: 'autoUpdate', + includeAssets: ['icon-192.png', 'icon-512.png', 'icon-1024.png', 'favicon.ico'], + manifest: { + name: 'SIBU - Sistema de Transporte', + short_name: 'SIBU', + description: 'Sistema de Transporte Público', + theme_color: '#fee715', + background_color: '#ffffff', + display: 'standalone', + orientation: 'portrait', + scope: '/', + start_url: '/', + icons: [ + { + src: 'icon-192.png', + sizes: '192x192', + type: 'image/png', + }, + { + src: 'icon-512.png', + sizes: '512x512', + type: 'image/png', + }, + { + src: 'icon-1024.png', + sizes: '1024x1024', + type: 'image/png', + }, + ], + }, + workbox: { + globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'], + runtimeCaching: [ + { + urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i, + handler: 'CacheFirst', + options: { + cacheName: 'google-fonts-cache', + expiration: { + maxEntries: 10, + maxAgeSeconds: 60 * 60 * 24 * 365, // 1 year + }, + cacheableResponse: { + statuses: [0, 200], + }, }, }, - }, - { - urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i, - handler: 'CacheFirst', - options: { - cacheName: 'gstatic-fonts-cache', - expiration: { - maxEntries: 10, - maxAgeSeconds: 60 * 60 * 24 * 365, // 1 year - }, - cacheableResponse: { - statuses: [0, 200], + { + urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i, + handler: 'CacheFirst', + options: { + cacheName: 'gstatic-fonts-cache', + expiration: { + maxEntries: 10, + maxAgeSeconds: 60 * 60 * 24 * 365, // 1 year + }, + cacheableResponse: { + statuses: [0, 200], + }, }, }, - }, - ], + ], + }, + devOptions: { + enabled: false, + type: 'module', + }, + }), + ], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), }, - devOptions: { - enabled: false, - type: 'module', - }, - }), - ], - resolve: { - alias: { - '@': path.resolve(__dirname, './src'), }, - }, + build: { + // Ajustar límite de advertencia (MapView + Google Maps son grandes por naturaleza) + chunkSizeWarningLimit: 700, + rollupOptions: { + output: { + // Dividir código en chunks lógicos para cargas más rápidas en producción + manualChunks: (id) => { + // Vendor: Firebase (bundle más pesado del stack) + if (id.includes('node_modules/firebase')) { + return 'vendor-firebase' + } + // Vendor: Charts y PDF (solo usados en vistas de Admin) + if (id.includes('node_modules/chart.js') || id.includes('node_modules/vue-chartjs')) { + return 'vendor-charts' + } + if (id.includes('node_modules/jspdf') || id.includes('node_modules/html2canvas')) { + return 'vendor-pdf' + } + // Vendor: Ecosistema Vue (vue, pinia, router, i18n) + if ( + id.includes('node_modules/vue') || + id.includes('node_modules/pinia') || + id.includes('node_modules/vue-router') || + id.includes('node_modules/vue-i18n') + ) { + return 'vendor-vue' + } + // Vendor: Google Maps loader + if (id.includes('node_modules/@googlemaps')) { + return 'vendor-maps' + } + }, + }, + }, + }, + } })