import react from '@vitejs/plugin-react'; import { codeInspectorPlugin } from 'code-inspector-plugin'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; import tsconfigPaths from 'vite-tsconfig-paths'; import { viteEmotionSpeedy } from './emotionSpeedy'; import { viteNodeModuleStub } from './nodeModuleStub'; import { vitePlatformResolve } from './platformResolve'; /** * Shared manualChunks — groups leaf-node modules to reduce chunk file count. * Only targets pure data modules (no downstream dependents) to avoid facade chunk issues. */ /** Large i18n namespaces that get their own per-locale chunk instead of merging into the locale bundle */ const HEAVY_NS = new Set(['models', 'modelProvider']); /** antd locale filename → app locale */ const ANTD_LOCALE: Record = { ar_EG: 'ar', bg_BG: 'bg-BG', de_DE: 'de-DE', en_US: 'en-US', es_ES: 'es-ES', fa_IR: 'fa-IR', fr_FR: 'fr-FR', it_IT: 'it-IT', ja_JP: 'ja-JP', ko_KR: 'ko-KR', nl_NL: 'nl-NL', pl_PL: 'pl-PL', pt_BR: 'pt-BR', ru_RU: 'ru-RU', tr_TR: 'tr-TR', vi_VN: 'vi-VN', zh_CN: 'zh-CN', zh_TW: 'zh-TW', }; /** dayjs locale filename → app locale */ const DAYJS_LOCALE: Record = { 'ar': 'ar', 'bg': 'bg-BG', 'de': 'de-DE', 'en': 'en-US', 'es': 'es-ES', 'fa': 'fa-IR', 'fr': 'fr-FR', 'it': 'it-IT', 'ja': 'ja-JP', 'ko': 'ko-KR', 'nl': 'nl-NL', 'pl': 'pl-PL', 'pt-br': 'pt-BR', 'ru': 'ru-RU', 'tr': 'tr-TR', 'vi': 'vi-VN', 'zh-cn': 'zh-CN', 'zh-tw': 'zh-TW', }; function sharedManualChunks(id: string): string | undefined { // i18n locale JSON/TS files const localeMatch = id.match(/\/locales\/([^/]+)\/([^/.]+)/); if (localeMatch) { const [, locale, ns] = localeMatch; if (locale === 'default') return 'i18n-default'; if (HEAVY_NS.has(ns)) return `i18n-${locale}-${ns}`; return `i18n-${locale}`; } // model-bank (monorepo package — split before node_modules guard) if (id.includes('model-bank')) return 'providerConfig'; if (!id.includes('node_modules')) return; // antd locale → merge into i18n-{locale} const antdMatch = id.match(/antd\/es\/locale\/([^/.]+)\.js/); if (antdMatch) { const locale = ANTD_LOCALE[antdMatch[1]]; if (locale) return `i18n-${locale}`; } // dayjs locale → merge into i18n-{locale} const dayjsMatch = id.match(/dayjs\/locale\/([^/.]+)\.js/); if (dayjsMatch) { const locale = DAYJS_LOCALE[dayjsMatch[1]]; if (locale) return `i18n-${locale}`; } // Lucide icons if (id.includes('lucide-react')) return 'vendor-icons'; // es-toolkit if (id.includes('es-toolkit')) return 'vendor-es-toolkit'; // emotion (CSS-in-JS runtime) if (id.includes('@emotion/')) return 'vendor-emotion'; // motion (framer-motion) if (id.includes('/motion/') || id.includes('framer-motion')) return 'vendor-motion'; } export const sharedRollupOutput = { chunkFileNames: (chunkInfo: { name: string }) => { const { name } = chunkInfo; if (name.startsWith('i18n-')) return 'i18n/[name]-[hash].js'; if (name.startsWith('vendor-')) return 'vendor/[name]-[hash].js'; return 'assets/[name]-[hash].js'; }, manualChunks: sharedManualChunks, }; type Platform = 'web' | 'mobile' | 'desktop'; const isDev = process.env.NODE_ENV !== 'production'; interface SharedRendererOptions { platform: Platform; tsconfigPaths?: boolean; } export function sharedRendererPlugins(options: SharedRendererOptions) { const defaultTsconfigPaths = options.tsconfigPaths ?? true; return [ viteEmotionSpeedy(), nodePolyfills({ include: ['buffer'] }), viteNodeModuleStub(), vitePlatformResolve(options.platform), defaultTsconfigPaths && tsconfigPaths({ projects: ['.'] }), isDev && codeInspectorPlugin({ bundler: 'vite', exclude: [/\.(css|json)$/], hotKeys: ['altKey', 'ctrlKey'], }), react(), ]; } export function sharedRendererDefine(options: { isElectron: boolean; isMobile: boolean }) { const nextPublicDefine = Object.fromEntries( Object.entries(process.env) .filter(([key]) => key.toUpperCase().startsWith('NEXT_PUBLIC_')) .map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]), ); return { '__CI__': process.env.CI === 'true' ? 'true' : 'false', '__DEV__': process.env.NODE_ENV !== 'production' ? 'true' : 'false', '__ELECTRON__': JSON.stringify(options.isElectron), '__MOBILE__': JSON.stringify(options.isMobile), ...nextPublicDefine, // Keep a safe fallback so generic `process.env` access won't crash in browser runtime. 'process.env': '{}', }; } export const sharedOptimizeDeps = { include: [ 'react', 'react-dom', 'react-dom/client', 'react-router-dom', 'antd', '@ant-design/icons', '@lobehub/ui', '@lobehub/ui > @emotion/react', 'antd-style', 'zustand', 'zustand/middleware', 'swr', 'i18next', 'react-i18next', 'dayjs', 'dayjs/esm/locale/ar', 'dayjs/esm/locale/bg', 'dayjs/esm/locale/de', 'dayjs/esm/locale/en', 'dayjs/esm/locale/es', 'dayjs/esm/locale/fa', 'dayjs/esm/locale/fr', 'dayjs/esm/locale/it', 'dayjs/esm/locale/ja', 'dayjs/esm/locale/ko', 'dayjs/esm/locale/nl', 'dayjs/esm/locale/pl', 'dayjs/esm/locale/pt-br', 'dayjs/esm/locale/ru', 'dayjs/esm/locale/tr', 'dayjs/esm/locale/vi', 'dayjs/esm/locale/zh-cn', 'dayjs/esm/locale/zh-tw', 'ahooks', 'motion/react', ], };