import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import { VitePWA } from "vite-plugin-pwa"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [ react(), VitePWA({ injectRegister: "auto", includeAssets: [ "icon.svg", "icons/icon-192x192.png", "icons/icon-512x512.png", ], manifest: { name: "NoteJS - Система заметок", short_name: "NoteJS", description: "Современная система заметок с поддержкой Markdown, изображений, тегов и календаря", theme_color: "#007bff", background_color: "#ffffff", display: "standalone", orientation: "portrait-primary", scope: "/", start_url: "/", icons: [ { src: "/icons/icon-72x72.png", sizes: "72x72", type: "image/png", purpose: "any", }, { src: "/icons/icon-96x96.png", sizes: "96x96", type: "image/png", purpose: "any", }, { src: "/icons/icon-128x128.png", sizes: "128x128", type: "image/png", purpose: "any", }, { src: "/icons/icon-144x144.png", sizes: "144x144", type: "image/png", purpose: "any", }, { src: "/icons/icon-152x152.png", sizes: "152x152", type: "image/png", purpose: "any", }, { src: "/icons/icon-192x192.png", sizes: "192x192", type: "image/png", purpose: "any", }, { src: "/icons/icon-384x384.png", sizes: "384x384", type: "image/png", purpose: "any", }, { src: "/icons/icon-512x512.png", sizes: "512x512", type: "image/png", purpose: "any", }, { src: "/icons/icon-192x192.png", sizes: "192x192", type: "image/png", purpose: "maskable", }, { src: "/icons/icon-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable", }, ], }, workbox: { globPatterns: ["**/*.{js,css,html,ico,png,svg,woff,woff2,ttf,eot}"], // Игнорируем параметры URL при кешировании ignoreURLParametersMatching: [/^utm_/, /^fbclid$/], // Обработка навигации - всегда используем кэшированную версию navigateFallback: "/index.html", navigateFallbackDenylist: [/^\/api/, /^\/uploads/], // Стратегия для навигационных запросов - сначала кэш navigationPreload: false, // Обработка ошибок при загрузке файлов для precaching dontCacheBustURLsMatching: /\.\w{8}\./, // Фильтруем манифест, чтобы исключить несуществующие файлы и дубликаты manifestTransforms: [ async (manifestEntries) => { // Фильтруем дубликаты const seen = new Set(); const filtered = manifestEntries.filter((entry) => { // Удаляем дубликаты if (seen.has(entry.url)) { return false; } seen.add(entry.url); return true; }); return { manifest: filtered, warnings: [] }; }, ], runtimeCaching: [ // HTML документы - всегда сначала кэш для оффлайн работы // Это гарантирует, что при обновлении страницы в оффлайн режиме // всегда будет загружаться кэшированная версия БЕЗ попыток обращения к сети { urlPattern: ({ request }) => request.mode === 'navigate', handler: "CacheFirst", options: { cacheName: "pages-cache", expiration: { maxEntries: 10, maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days }, cacheableResponse: { statuses: [0, 200], }, }, }, // Дополнительное правило для HTML файлов { urlPattern: /\.html$/, handler: "CacheFirst", options: { cacheName: "html-cache", expiration: { maxEntries: 10, maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days }, cacheableResponse: { statuses: [0, 200], }, }, }, { urlPattern: /^https:\/\/api\./, handler: "NetworkFirst", options: { cacheName: "api-cache", expiration: { maxEntries: 50, maxAgeSeconds: 60 * 60, // 1 hour }, }, }, { urlPattern: /\/api\//, handler: "NetworkFirst", options: { cacheName: "api-cache-local", expiration: { maxEntries: 100, maxAgeSeconds: 24 * 60 * 60, // 24 hours }, networkTimeoutSeconds: 10, }, }, { urlPattern: /\/uploads\//, handler: "CacheFirst", options: { cacheName: "uploads-cache", expiration: { maxEntries: 200, maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days }, }, }, { urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp)$/, handler: "CacheFirst", options: { cacheName: "images-cache", expiration: { maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days }, }, }, ], cleanupOutdatedCaches: true, skipWaiting: true, clientsClaim: true, // Обработка ошибок при precaching - игнорируем 404 ошибки maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // 5MB }, // Автоматическое обновление SW без запроса пользователя // для обеспечения стабильной работы в оффлайн режиме registerType: "autoUpdate", devOptions: { enabled: true, type: "module", }, }), ], server: { port: 5173, allowedHosts: ["app.notejs.ru", "localhost"], proxy: { "/api": { target: "http://localhost:3001", changeOrigin: true, secure: false, ws: true, }, "/uploads": { target: "http://localhost:3001", changeOrigin: true, secure: false, }, "/logout": { target: "http://localhost:3001", changeOrigin: true, secure: false, }, }, }, });