import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { VitePWA } from 'vite-plugin-pwa' import Components from 'unplugin-vue-components/vite' import AutoImport from 'unplugin-auto-import/vite' import { quasar, transformAssetUrls } from '@quasar/vite-plugin' import path from 'path' export default defineConfig({ plugins: [ vue({ template: { transformAssetUrls } }), quasar({ // sassVariables: 'src/assets/styles/quasar-variables.sass' }), Components({ resolvers: [ (componentName) => { if (componentName.startsWith('Q')) return { name: componentName, from: 'quasar' } } ], dts: true, dirs: ['src/components'], extensions: ['vue'], deep: true }), AutoImport({ imports: [ 'vue', 'vue-router', 'pinia', { 'quasar': ['useQuasar', '$q', 'Notify', 'Loading', 'Dialog'], '@vueuse/core': ['useLocalStorage', 'useSessionStorage', 'useFetch'] } ], dts: true, dirs: ['src/composables', 'src/stores'], vueTemplate: true }), VitePWA({ registerType: 'autoUpdate', workbox: { globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'], skipWaiting: true, clientsClaim: true, runtimeCaching: [ { urlPattern: /^https:\/\/api\..*/i, handler: 'NetworkFirst', options: { cacheName: 'api-cache', networkTimeoutSeconds: 10, expiration: { maxEntries: 50, maxAgeSeconds: 5 * 60, // 5 minutes }, cacheableResponse: { statuses: [0, 200], }, }, }, { urlPattern: /\.(?:png|gif|jpg|jpeg|svg|webp)$/, handler: 'CacheFirst', options: { cacheName: 'images-cache', expiration: { maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days }, }, }, ], }, manifest: { name: 'Drama Ling - 戲劇式語言學習', short_name: 'Drama Ling', description: '透過情境對話和互動練習學習語言的 AI 驅動應用程式', theme_color: '#00E5CC', background_color: '#1A1A1A', display: 'standalone', orientation: 'portrait', scope: '/', start_url: '/learning', categories: ['education', 'productivity'], lang: 'zh-TW', screenshots: [ { src: '/icons/screenshot-wide.png', sizes: '1280x720', type: 'image/png', form_factor: 'wide', label: 'Drama Ling 學習介面' } ], shortcuts: [ { name: '詞彙學習', short_name: '詞彙', description: '開始詞彙練習', url: '/learning/vocabulary', icons: [{ src: '/icons/shortcut-vocabulary.png', sizes: '96x96' }] }, { name: '智能複習', short_name: '複習', description: '進行智能複習', url: '/learning/vocabulary/review', icons: [{ src: '/icons/shortcut-review.png', sizes: '96x96' }] } ], icons: [ { src: '/favicon.svg', sizes: 'any', type: 'image/svg+xml' }, { src: '/icons/icon-192x192.svg', sizes: '192x192', type: 'image/svg+xml' }, { src: '/icons/icon-512x512.svg', sizes: '512x512', type: 'image/svg+xml' } ] }, devOptions: { enabled: false, // 只在生產環境啟用 type: 'module', navigateFallback: 'index.html' } }) ], resolve: { alias: { '@': path.resolve(__dirname, 'src'), '~': path.resolve(__dirname, 'src'), '@components': path.resolve(__dirname, 'src/components'), '@modules': path.resolve(__dirname, 'src/modules'), '@stores': path.resolve(__dirname, 'src/stores'), '@services': path.resolve(__dirname, 'src/services'), '@utils': path.resolve(__dirname, 'src/utils'), '@assets': path.resolve(__dirname, 'src/assets') } }, css: { preprocessorOptions: { scss: { additionalData: `@import "@/assets/styles/variables.scss";` } } }, build: { target: 'es2020', rollupOptions: { output: { manualChunks: { 'vue-vendor': ['vue', 'vue-router', 'pinia'], 'quasar-vendor': ['quasar'], 'utils-vendor': ['axios', 'lodash-es', 'dayjs', '@vueuse/core'], 'validation-vendor': ['vee-validate', 'yup'] } } } }, server: { host: '0.0.0.0', port: 3000, proxy: { '/api': { target: 'http://localhost:5000', changeOrigin: true } } } })