import fs from 'node:fs' import path from 'path' import { defineConfig, type Plugin } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' /** * Копирует wasm-рантайм MediaPipe (`@mediapipe/tasks-vision`) в `public/mediapipe/wasm/`, * откуда его забирает замена фона (`lib/virtualBackground.ts`). * * Зачем вообще: библиотека `@livekit/track-processors` по умолчанию тянет и * wasm, и модель сегментации с ВНЕШНИХ CDN (jsdelivr + storage.googleapis.com). * VidConf ставят в закрытых контурах, где внешнего интернета нет, — там фича * молча не заработала бы. Поэтому все ассеты отдаются со своего домена. * * Зачем копировать, а не коммитить: два варианта рантайма (simd/nosimd) весят * ~19 МБ — в репозитории им не место, а версия обязана совпадать с * установленной в `node_modules` (иначе рантайм и `vision_bundle.mjs` * разъедутся). Копия делается и в dev (`vite`), и в сборке (`vite build`, * который потом переносит весь `public/` в `dist/`), каталог назначения * gitignored. Модель сегментации (`selfie_segmenter.tflite`, ~244 КБ) npm-пакетом * не поставляется — она лежит в репозитории рядом, см. `public/mediapipe/NOTICE.txt`. */ function mediapipeWasm(): Plugin { const source = path.resolve(__dirname, 'node_modules/@mediapipe/tasks-vision/wasm') const target = path.resolve(__dirname, 'public/mediapipe/wasm') return { name: 'vidconf-mediapipe-wasm', buildStart() { if (!fs.existsSync(source)) { // Пакет не установлен — падать сборкой не за что: замена фона просто // не сможет инициализироваться, остальное приложение работает. this.warn(`не найден wasm-рантайм MediaPipe: ${source}`) return } fs.mkdirSync(target, { recursive: true }) for (const name of fs.readdirSync(source)) { const from = path.join(source, name) const to = path.join(target, name) // Пропускаем уже скопированное с тем же размером — иначе каждый // перезапуск dev-сервера переписывал бы 19 МБ. const src = fs.statSync(from) const dst = fs.existsSync(to) ? fs.statSync(to) : null if (dst && dst.size === src.size) continue fs.copyFileSync(from, to) } }, } } // https://vite.dev/config/ export default defineConfig({ plugins: [react(), tailwindcss(), mediapipeWasm()], resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, server: { proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true, // WS-чат конференции ходит через тот же префикс /api ws: true, }, // Загруженные файлы (аватары, картинки фона). На проде их раздаёт nginx // напрямую из тома (`location /media/`), минуя backend; в dev такого // слоя нет, и без этого прокси все `/media/...` упирались бы в SPA // и отдавали index.html вместо картинки. // // ⚠️ Ключ обязан быть со СЛЭШЕМ на конце. Vite матчит префиксом, и // ключ `/media` перехватывал заодно `/mediapipe/...` — ассеты замены // фона уходили на backend и получали 404, из-за чего фон молча не // включался. У nginx та же семантика: там `location /media/` тоже // со слэшем, поэтому прод этим не задет. '/media/': { target: 'http://localhost:8000', changeOrigin: true, }, }, }, })