/** * mini-user H5 本地预览: * Taro Vite `--watch` 在本机易 OOM(峰值 >8GB),改为: * 1) 先完整 build(约 30s) * 2) 静态托管 dist + /api 代理 * 3) 监听 src 变更后防抖重建 */ import { spawn } from 'node:child_process'; import fs from 'node:fs'; import http from 'node:http'; import https from 'node:https'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const APP_ROOT = path.resolve(__dirname, '../apps/mini-user'); const DIST = path.resolve(APP_ROOT, 'dist'); const PORT = Number(process.env.PORT || 5177); const API_ORIGIN = (process.env.VITE_API_TARGET ?? 'https://dkapi.runxian.top').replace(/\/$/, ''); const TARO_BIN = path.resolve(APP_ROOT, 'node_modules/@tarojs/cli/bin/taro'); const MIME = { '.html': 'text/html; charset=utf-8', '.js': 'application/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.json': 'application/json; charset=utf-8', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.woff': 'font/woff', '.woff2': 'font/woff2', '.map': 'application/json', }; function runBuild() { return new Promise((resolve, reject) => { const child = spawn( process.execPath, ['--max-old-space-size=8192', TARO_BIN, 'build', '--type', 'h5'], { cwd: APP_ROOT, stdio: 'inherit', env: process.env, }, ); child.on('exit', (code) => { if (code === 0) resolve(); else reject(new Error(`taro build failed with code ${code}`)); }); }); } function sendFile(res, filePath) { const ext = path.extname(filePath).toLowerCase(); res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' }); fs.createReadStream(filePath).pipe(res); } function proxyApi(req, res) { const target = new URL(req.url || '/', API_ORIGIN); const lib = target.protocol === 'https:' ? https : http; const headers = { ...req.headers, host: target.host }; delete headers['accept-encoding']; const upstream = lib.request( { protocol: target.protocol, hostname: target.hostname, port: target.port || undefined, path: target.pathname + target.search, method: req.method, headers, }, (up) => { res.writeHead(up.statusCode || 502, up.headers); up.pipe(res); }, ); upstream.on('error', (err) => { res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end(`API proxy error: ${err.message}`); }); req.pipe(upstream); } function startServer() { const server = http.createServer((req, res) => { const urlPath = decodeURIComponent((req.url || '/').split('?')[0]); if (urlPath.startsWith('/api')) { proxyApi(req, res); return; } const candidates = [ path.join(DIST, urlPath), path.join(DIST, urlPath, 'index.html'), path.join(DIST, 'index.html'), ]; const file = candidates.find((p) => fs.existsSync(p) && fs.statSync(p).isFile()); if (!file) { res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('Not Found'); return; } sendFile(res, file); }); server.listen(PORT, '0.0.0.0', () => { console.log(`\n mini-user H5 preview: http://localhost:${PORT}`); console.log(` API proxy -> ${API_ORIGIN}`); console.log(' Watching apps/mini-user/src for changes...\n'); }); } function watchSrc() { const srcDir = path.join(APP_ROOT, 'src'); let timer = null; let building = false; let queued = false; const schedule = () => { if (timer) clearTimeout(timer); timer = setTimeout(async () => { if (building) { queued = true; return; } building = true; try { console.log('\n[dev] src changed, rebuilding...'); await runBuild(); console.log('[dev] rebuild done — refresh browser'); } catch (e) { console.error('[dev] rebuild failed:', e instanceof Error ? e.message : e); } finally { building = false; if (queued) { queued = false; schedule(); } } }, 800); }; fs.watch(srcDir, { recursive: true }, (_event, filename) => { if (!filename) return; if (/\.(tsx?|jsx?|css|scss|json|png|jpg|svg)$/i.test(filename)) { schedule(); } }); } async function main() { if (!fs.existsSync(TARO_BIN)) { throw new Error(`Taro CLI not found: ${TARO_BIN}`); } console.log('[dev] initial H5 build...'); await runBuild(); startServer(); watchSrc(); } main().catch((e) => { console.error(e); process.exit(1); });