mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
channel: master version: 1.1.0 source-ref: master published-at-utc: 2026-03-20T14:14:07Z
237 lines
6.1 KiB
JavaScript
237 lines
6.1 KiB
JavaScript
import { spawn, spawnSync } from 'node:child_process'
|
|
import { dirname, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const scriptDir = dirname(fileURLToPath(import.meta.url))
|
|
const frontendDir = resolve(scriptDir, '..')
|
|
const defaultVitePort = 5218
|
|
const defaultMaxOldSpaceSizeMb = 256
|
|
const defaultMaxSemiSpaceSizeMb = 16
|
|
const defaultRssWarnMb = 256
|
|
const defaultRssHardLimitMb = 360
|
|
const defaultMemoryPollMs = 3000
|
|
const nodeExecutable = process.execPath
|
|
const ensureNativeScript = resolve(frontendDir, 'scripts', 'ensure-rollup-native.mjs')
|
|
const viteEntry = resolve(frontendDir, 'node_modules', 'vite', 'bin', 'vite.js')
|
|
|
|
function ensureNativeRuntime(env) {
|
|
const result = spawnSync(nodeExecutable, [ensureNativeScript], {
|
|
cwd: frontendDir,
|
|
stdio: 'inherit',
|
|
env,
|
|
})
|
|
|
|
if (result.error) {
|
|
throw result.error
|
|
}
|
|
if ((result.status ?? 0) !== 0) {
|
|
throw new Error(`ensure native failed with exit code ${result.status ?? 1}`)
|
|
}
|
|
}
|
|
|
|
function resolveRequestedPort(rawPort, fallbackPort = defaultVitePort) {
|
|
const parsed = Number.parseInt(String(rawPort || '').trim(), 10)
|
|
if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) {
|
|
return parsed
|
|
}
|
|
return fallbackPort
|
|
}
|
|
|
|
function resolvePositiveInteger(rawValue, fallbackValue) {
|
|
const parsed = Number.parseInt(String(rawValue || '').trim(), 10)
|
|
if (Number.isInteger(parsed) && parsed > 0) {
|
|
return parsed
|
|
}
|
|
return fallbackValue
|
|
}
|
|
|
|
function resolveNodeArgs(env) {
|
|
const maxOldSpaceSizeMb = resolvePositiveInteger(
|
|
env.FRONTEND_NODE_MAX_OLD_SPACE_SIZE_MB,
|
|
defaultMaxOldSpaceSizeMb,
|
|
)
|
|
const maxSemiSpaceSizeMb = resolvePositiveInteger(
|
|
env.FRONTEND_NODE_MAX_SEMI_SPACE_SIZE_MB,
|
|
defaultMaxSemiSpaceSizeMb,
|
|
)
|
|
|
|
const args = [`--max-old-space-size=${maxOldSpaceSizeMb}`]
|
|
if (maxSemiSpaceSizeMb > 0) {
|
|
args.push(`--max-semi-space-size=${maxSemiSpaceSizeMb}`)
|
|
}
|
|
if (String(env.FRONTEND_NODE_HEAP_SNAPSHOT || '').trim() === '1') {
|
|
args.push('--heapsnapshot-near-heap-limit=2')
|
|
}
|
|
args.push(viteEntry)
|
|
|
|
return {
|
|
args,
|
|
maxOldSpaceSizeMb,
|
|
maxSemiSpaceSizeMb,
|
|
}
|
|
}
|
|
|
|
function killProcessTree(pid) {
|
|
if (!pid || pid <= 0) {
|
|
return
|
|
}
|
|
|
|
if (process.platform === 'win32') {
|
|
spawnSync('taskkill.exe', ['/F', '/T', '/PID', String(pid)], {
|
|
stdio: 'ignore',
|
|
})
|
|
return
|
|
}
|
|
|
|
try {
|
|
process.kill(pid, 'SIGTERM')
|
|
} catch {}
|
|
}
|
|
|
|
function readProcessRssMb(pid) {
|
|
if (!pid || pid <= 0) {
|
|
return 0
|
|
}
|
|
|
|
if (process.platform === 'win32') {
|
|
const result = spawnSync(
|
|
'powershell.exe',
|
|
[
|
|
'-NoProfile',
|
|
'-Command',
|
|
`$proc = Get-Process -Id ${pid} -ErrorAction SilentlyContinue; if ($proc) { [math]::Round($proc.WorkingSet64 / 1MB, 0) }`,
|
|
],
|
|
{
|
|
cwd: frontendDir,
|
|
encoding: 'utf8',
|
|
},
|
|
)
|
|
|
|
if (result.status !== 0) {
|
|
return 0
|
|
}
|
|
|
|
return resolvePositiveInteger(result.stdout, 0)
|
|
}
|
|
|
|
const result = spawnSync('ps', ['-o', 'rss=', '-p', String(pid)], {
|
|
cwd: frontendDir,
|
|
encoding: 'utf8',
|
|
})
|
|
if (result.status !== 0) {
|
|
return 0
|
|
}
|
|
|
|
const rssKb = resolvePositiveInteger(result.stdout, 0)
|
|
return Math.round(rssKb / 1024)
|
|
}
|
|
|
|
function startMemoryWatcher(child, env) {
|
|
const rssWarnMb = resolvePositiveInteger(env.FRONTEND_NODE_RSS_WARN_MB, defaultRssWarnMb)
|
|
const rssHardLimitMb = resolvePositiveInteger(env.FRONTEND_NODE_RSS_HARD_LIMIT_MB, defaultRssHardLimitMb)
|
|
const pollMs = resolvePositiveInteger(env.FRONTEND_NODE_MEMORY_POLL_MS, defaultMemoryPollMs)
|
|
let warnedAtMb = 0
|
|
|
|
const timer = setInterval(() => {
|
|
if (!child.pid || child.exitCode !== null) {
|
|
return
|
|
}
|
|
|
|
const rssMb = readProcessRssMb(child.pid)
|
|
if (rssMb <= 0) {
|
|
return
|
|
}
|
|
|
|
if (rssMb >= rssWarnMb && (warnedAtMb === 0 || Math.abs(rssMb - warnedAtMb) >= 128)) {
|
|
warnedAtMb = rssMb
|
|
console.warn(`[dev] vite RSS is ${rssMb} MB (warning threshold: ${rssWarnMb} MB)`)
|
|
}
|
|
|
|
if (rssHardLimitMb > 0 && rssMb >= rssHardLimitMb) {
|
|
console.error(`[dev] vite RSS reached ${rssMb} MB, exceeding hard limit ${rssHardLimitMb} MB. stopping dev server.`)
|
|
killProcessTree(child.pid)
|
|
}
|
|
}, pollMs)
|
|
|
|
timer.unref?.()
|
|
return timer
|
|
}
|
|
|
|
function main() {
|
|
const requestedPort = resolveRequestedPort(process.env.FRONTEND_PORT, defaultVitePort)
|
|
|
|
const childEnv = {
|
|
...process.env,
|
|
FRONTEND_PORT: String(requestedPort),
|
|
}
|
|
|
|
ensureNativeRuntime(childEnv)
|
|
|
|
const nodeArgs = resolveNodeArgs(childEnv)
|
|
console.log(
|
|
`[dev] starting Vite on http://127.0.0.1:${requestedPort} with --max-old-space-size=${nodeArgs.maxOldSpaceSizeMb} MB --max-semi-space-size=${nodeArgs.maxSemiSpaceSizeMb} MB --rss-hard-limit=${resolvePositiveInteger(childEnv.FRONTEND_NODE_RSS_HARD_LIMIT_MB, defaultRssHardLimitMb)} MB`,
|
|
)
|
|
|
|
const child = spawn(nodeExecutable, nodeArgs.args, {
|
|
cwd: frontendDir,
|
|
stdio: 'inherit',
|
|
env: childEnv,
|
|
})
|
|
const memoryWatcher = startMemoryWatcher(child, childEnv)
|
|
let shuttingDown = false
|
|
|
|
const shutdown = (exitCode = 0) => {
|
|
if (shuttingDown) {
|
|
return
|
|
}
|
|
shuttingDown = true
|
|
if (memoryWatcher) {
|
|
clearInterval(memoryWatcher)
|
|
}
|
|
if (child.pid && child.exitCode === null) {
|
|
killProcessTree(child.pid)
|
|
}
|
|
process.exit(exitCode)
|
|
}
|
|
|
|
const handleSignal = (signal) => {
|
|
console.log(`[dev] received ${signal}, stopping Vite...`)
|
|
shutdown(0)
|
|
}
|
|
|
|
process.on('SIGINT', handleSignal)
|
|
process.on('SIGTERM', handleSignal)
|
|
process.on('exit', () => {
|
|
if (memoryWatcher) {
|
|
clearInterval(memoryWatcher)
|
|
}
|
|
if (child.pid && child.exitCode === null) {
|
|
killProcessTree(child.pid)
|
|
}
|
|
})
|
|
|
|
child.on('error', (error) => {
|
|
console.error(`[dev] failed to start Vite: ${error instanceof Error ? error.message : String(error)}`)
|
|
shutdown(1)
|
|
})
|
|
|
|
child.on('exit', (code, signal) => {
|
|
if (memoryWatcher) {
|
|
clearInterval(memoryWatcher)
|
|
}
|
|
if (signal) {
|
|
console.error(`[dev] vite exited with signal ${signal}`)
|
|
process.exit(1)
|
|
return
|
|
}
|
|
process.exit(code ?? 0)
|
|
})
|
|
}
|
|
|
|
try {
|
|
main()
|
|
} catch (error) {
|
|
console.error(`[dev] ${error instanceof Error ? error.message : String(error)}`)
|
|
process.exit(1)
|
|
}
|