import { defineConfig } from 'vite'; import { VitePWA } from 'vite-plugin-pwa'; export default defineConfig({ // @sqlite.org/sqlite-wasm: // - JS-часть идёт как ESM (sqlite3-bundler-friendly.mjs), // - .wasm подхватывается относительно import.meta.url. // Vite из коробки копирует такие ассеты в dist и переписывает URL, // явно добавляем .wasm в assetsInclude и не оптимизируем пакет, // чтобы dev-сервер отдавал оригинальный .wasm байт-в-байт. assetsInclude: ['**/*.wasm'], optimizeDeps: { exclude: ['@sqlite.org/sqlite-wasm'], }, worker: { // Воркер БД (Шаг 2) будет в формате ESM — так sqlite-wasm работает корректно. format: 'es', }, server: { headers: { // Нужно для OPFS + SharedArrayBuffer (sqlite-wasm использует воркеры). // COOP/COEP обязательны для crossOriginIsolated. 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp', }, }, preview: { // Те же заголовки для `vite preview` — иначе прод-сборку локально // не проверить: без COOP/COEP не будет crossOriginIsolated и OPFS-БД. headers: { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp', }, }, plugins: [ { name: 'configure-response-headers', configureServer: (server) => { server.middlewares.use((_req, res, next) => { res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); next(); }); }, }, // PWA: манифест + precache app shell (Workbox) + sw.js. // Регистрацию SW делаем сами (injectRegister: null) — см. src/pwa.ts, // чтобы показать баннер «Доступна новая версия». VitePWA({ registerType: 'prompt', injectRegister: null, manifest: { id: '/', name: 'JapLearn — интервальные повторения', short_name: 'JapLearn', description: 'Японские слова, колоды и интервальные повторения. Работает офлайн.', lang: 'ru', dir: 'ltr', start_url: '/', scope: '/', display: 'standalone', orientation: 'portrait', background_color: '#282828', theme_color: '#282828', categories: ['education'], icons: [ { src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any' }, { src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any' }, { src: '/icons/maskable-192.png', sizes: '192x192', type: 'image/png', purpose: 'maskable' }, { src: '/icons/maskable-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' }, ], }, workbox: { // В precache идёт вся сборка: js/css/html, sqlite-wasm, иконки, сплэши. globPatterns: ['**/*.{js,css,html,wasm,png,svg,webmanifest}'], // Шрифт (8.8 МБ variable TTF) в precache не берём: он нужен только для // отрисовки, кладём его в runtime-кэш CacheFirst ниже. globIgnores: ['**/fonts/*.ttf'], maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // SPA без URL-роутинга: любая навигация отдаётся из кэша index.html. navigateFallback: '/index.html', cleanupOutdatedCaches: true, clientsClaim: true, runtimeCaching: [ { // Локальный шрифт: после первой загрузки работает офлайн. urlPattern: /\/fonts\/.*\.ttf$/, handler: 'CacheFirst', options: { cacheName: 'japlearn-fonts', expiration: { maxEntries: 4 }, cacheableResponse: { statuses: [0, 200] }, }, }, { // Синхронизация (скачивание .sqlite) — только сеть, никакого кэша. urlPattern: /^https:\/\/learning\.micialware\.ru\//, handler: 'NetworkOnly', }, ], }, devOptions: { // В dev SW не нужен и мешает отладке кэша (HMR + OPFS). enabled: false, }, }), ], });