import { readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { defineConfig } from 'vitest/config'; import type { Plugin } from 'vite'; import react from '@vitejs/plugin-react'; const PYODIDE_ASSETS = [ 'pyodide.asm.js', 'pyodide.asm.wasm', 'python_stdlib.zip', 'pyodide-lock.json', ] as const; const pyodideDirectory = resolve( dirname(fileURLToPath(import.meta.url)), '../node_modules/pyodide', ); /** 生产预览中长期缓存带内容哈希的资源,同时允许入口文件及时更新。 */ function previewCacheHeaders(): Plugin { return { name: 'preview-cache-headers', configurePreviewServer(server) { server.middlewares.use((request, response, next) => { const path = request.url?.split(/[?#]/, 1)[0] ?? ''; if (path.startsWith('/assets/')) response.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); else if (path.startsWith('/pyodide/')) response.setHeader( 'Cache-Control', 'public, max-age=86400, stale-while-revalidate=604800', ); else response.setHeader('Cache-Control', 'no-cache'); next(); }); }, }; } /** 让开发服务器与生产构建都从本地 npm 包提供 Pyodide,保持平台可离线部署。 */ function localPyodideAssets(): Plugin { let isBuild = false; return { name: 'local-pyodide-assets', configResolved(config) { isBuild = config.command === 'build'; }, configureServer(server) { server.middlewares.use((request, response, next) => { const name = request.url?.split(/[?#]/, 1)[0].replace(/^\/pyodide\//, ''); if (!name || !PYODIDE_ASSETS.includes(name as (typeof PYODIDE_ASSETS)[number])) { next(); return; } response.setHeader( 'Content-Type', name.endsWith('.wasm') ? 'application/wasm' : name.endsWith('.zip') ? 'application/zip' : name.endsWith('.json') ? 'application/json' : 'text/javascript', ); response.end(readFileSync(resolve(pyodideDirectory, name))); }); }, buildStart() { if (isBuild) for (const name of PYODIDE_ASSETS) this.emitFile({ type: 'asset', fileName: `pyodide/${name}`, source: readFileSync(resolve(pyodideDirectory, name)), }); }, }; } export default defineConfig(({ mode }) => ({ root: 'web_platform', base: './', plugins: [ react(), localPyodideAssets(), previewCacheHeaders(), ...(mode === 'website' ? [ { name: 'website-html', transformIndexHtml(html: string) { return html .replace( '