Files
ai-code-app/vite.config.ts
2026-04-21 03:33:23 +09:00

207 lines
5.9 KiB
TypeScript
Executable File

import { cpSync, existsSync, mkdirSync, readdirSync } from 'fs';
import { join } from 'path';
import { defineConfig, type ResolvedConfig, type ViteDevServer } from 'vite';
import react from '@vitejs/plugin-react';
import { VitePWA } from 'vite-plugin-pwa';
const DEV_APP_UPDATE_PATH = '/__app-update';
const processEnv =
(
globalThis as typeof globalThis & {
process?: {
env?: Record<string, string | undefined>;
};
}
).process?.env ?? {};
const VITE_PUBLIC_HMR_HOST = processEnv.VITE_PUBLIC_HMR_HOST ?? 'test.sm-home.cloud';
const VITE_PUBLIC_HMR_PROTOCOL = processEnv.VITE_PUBLIC_HMR_PROTOCOL ?? 'wss';
const VITE_PUBLIC_HMR_CLIENT_PORT = Number(processEnv.VITE_PUBLIC_HMR_CLIENT_PORT ?? 443);
const VITE_EMPTY_OUT_DIR = processEnv.VITE_EMPTY_OUT_DIR !== 'false';
const VITE_FILTER_PUBLIC_DIR = processEnv.VITE_FILTER_PUBLIC_DIR === 'true';
const VITE_DISABLE_MODULE_PRELOAD = processEnv.VITE_DISABLE_MODULE_PRELOAD === 'true';
function shouldIgnoreDevUpdatePath(watchedPath: string) {
return (
watchedPath.includes('/.auto_codex/') ||
watchedPath.includes('/dist/') ||
watchedPath.includes('/app-dist/') ||
watchedPath.includes('/test-app-dist/') ||
watchedPath.includes('/node_modules/') ||
watchedPath.includes('/public/.codex_chat/') ||
watchedPath.endsWith('/.server-command-runner-heartbeat.json')
);
}
function createDevAppUpdatePlugin() {
let updateToken = `${Date.now()}`;
const bumpUpdateToken = () => {
updateToken = `${Date.now()}`;
};
const shouldTrackPath = (watchedPath: string) => !shouldIgnoreDevUpdatePath(watchedPath);
return {
name: 'dev-app-update-probe',
apply: 'serve' as const,
configureServer(server: ViteDevServer) {
const handleFileChange = (file: string) => {
if (shouldTrackPath(file)) {
bumpUpdateToken();
}
};
server.watcher.on('add', handleFileChange);
server.watcher.on('change', handleFileChange);
server.watcher.on('unlink', handleFileChange);
server.middlewares.use(DEV_APP_UPDATE_PATH, (_request, response) => {
response.setHeader('Content-Type', 'application/json; charset=utf-8');
response.setHeader('Cache-Control', 'no-store');
response.end(
JSON.stringify({
token: updateToken,
updatedAt: new Date(Number(updateToken)).toISOString(),
}),
);
});
},
};
}
function copyPublicAssetsExceptCodexChat() {
let resolvedConfig: ResolvedConfig | null = null;
const copyDirectory = (sourceDir: string, targetDir: string) => {
if (!existsSync(sourceDir)) {
return;
}
mkdirSync(targetDir, { recursive: true });
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
if (entry.name === '.codex_chat') {
continue;
}
const sourcePath = join(sourceDir, entry.name);
const targetPath = join(targetDir, entry.name);
if (entry.isDirectory()) {
copyDirectory(sourcePath, targetPath);
continue;
}
if (entry.isFile()) {
mkdirSync(join(targetPath, '..'), { recursive: true });
cpSync(sourcePath, targetPath, { force: true });
}
}
};
return {
name: 'copy-public-assets-except-codex-chat',
apply: 'build' as const,
configResolved(config: ResolvedConfig) {
resolvedConfig = config;
},
closeBundle() {
if (!VITE_FILTER_PUBLIC_DIR || !resolvedConfig) {
return;
}
const publicDir = join(resolvedConfig.root, 'public');
const outDir = resolvedConfig.build.outDir.startsWith('/')
? resolvedConfig.build.outDir
: join(resolvedConfig.root, resolvedConfig.build.outDir);
copyDirectory(publicDir, outDir);
},
};
}
export default defineConfig({
build: {
copyPublicDir: !VITE_FILTER_PUBLIC_DIR,
emptyOutDir: VITE_EMPTY_OUT_DIR,
modulePreload: VITE_DISABLE_MODULE_PRELOAD ? false : undefined,
},
server: {
host: '0.0.0.0',
port: 5173,
hmr: {
host: VITE_PUBLIC_HMR_HOST,
protocol: VITE_PUBLIC_HMR_PROTOCOL as 'ws' | 'wss',
clientPort: VITE_PUBLIC_HMR_CLIENT_PORT,
},
allowedHosts: ['sm-home.cloud', 'test.sm-home.cloud', 'rel.sm-home.cloud'],
watch: {
ignored: (watchedPath) => shouldIgnoreDevUpdatePath(watchedPath),
},
proxy: {
'/api': {
target: 'http://work-server:3100',
changeOrigin: true,
},
'/ws/chat': {
target: 'ws://work-server:3100',
ws: true,
changeOrigin: true,
},
'/.codex_chat': {
target: 'http://work-server:3100',
changeOrigin: true,
},
},
},
preview: {
host: '0.0.0.0',
port: 5173,
allowedHosts: ['sm-home.cloud', 'test.sm-home.cloud', 'rel.sm-home.cloud'],
},
plugins: [
react(),
createDevAppUpdatePlugin(),
copyPublicAssetsExceptCodexChat(),
VitePWA({
strategies: 'injectManifest',
srcDir: 'src',
filename: 'sw.js',
registerType: 'prompt',
includeAssets: ['favicon.svg', 'apple-touch-icon.svg'],
injectManifest: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,webp,woff2}'],
globIgnores: ['**/.codex_chat/**'],
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
},
manifest: {
name: 'AI Code App',
short_name: 'AI Code App',
description: 'Ant Design 기반 UI 샘플과 문서를 확인하는 AI Code App',
theme_color: '#165dff',
background_color: '#eff5ff',
display: 'standalone',
lang: 'ko',
scope: '/',
start_url: '/',
icons: [
{
src: '/pwa-192x192.svg',
sizes: '192x192',
type: 'image/svg+xml',
},
{
src: '/pwa-512x512.svg',
sizes: '512x512',
type: 'image/svg+xml',
purpose: 'any maskable',
},
],
},
devOptions: {
enabled: true,
},
}),
],
});