perf: optimize app startup and background lifecycles

This commit is contained in:
inman
2026-08-19 00:56:15 +08:00
parent 31332f6d76
commit 927e13349b
121 changed files with 4359 additions and 1400 deletions

View File

@@ -8,7 +8,7 @@ const require = createRequire(import.meta.url);
const OUTPUT_LIMIT = 64 * 1024;
export class PublishRuntimeError extends Error {
constructor(readonly code: 'LOCAL_BUILD_RUNTIME_UNAVAILABLE' | 'LOCAL_BUILD_FAILED' | 'LOCAL_BUILD_TIMEOUT') {
constructor(readonly code: 'LOCAL_BUILD_RUNTIME_UNAVAILABLE' | 'LOCAL_BUILD_FAILED' | 'LOCAL_BUILD_TIMEOUT' | 'LOCAL_BUILD_CANCELLED') {
super(code);
this.name = 'PublishRuntimeError';
}
@@ -20,6 +20,13 @@ export type PublishRuntime = {
npmVersion: string;
};
export type PublishRuntimeContext = {
isPackaged: boolean;
resourcesPath: string;
};
const cachedPublishRuntimes = new Map<string, Promise<PublishRuntime>>();
function childEnvironment(): NodeJS.ProcessEnv {
const allowed = ['ALLUSERSPROFILE', 'APPDATA', 'COMMONPROGRAMFILES', 'COMMONPROGRAMFILES(X86)',
'COMMONPROGRAMW6432', 'COMSPEC', 'HOME', 'HOMEDRIVE', 'HOMEPATH', 'LOCALAPPDATA',
@@ -54,8 +61,13 @@ export async function runElectronNode(input: {
args: string[];
cwd?: string;
deadlineMs: number;
signal?: AbortSignal;
}): Promise<{ stdout: string; stderr: string }> {
return await new Promise((resolve, reject) => {
if (input.signal?.aborted) {
reject(new PublishRuntimeError('LOCAL_BUILD_CANCELLED'));
return;
}
const child = spawn(process.execPath, input.args, {
cwd: input.cwd,
env: childEnvironment(),
@@ -70,27 +82,57 @@ export async function runElectronNode(input: {
child.stdout?.on('data', (chunk: Buffer) => { stdout = append(stdout, chunk); });
child.stderr?.on('data', (chunk: Buffer) => { stderr = append(stderr, chunk); });
let timedOut = false;
let cancelled = false;
let settled = false;
const abort = () => {
cancelled = true;
void terminateTree(child);
};
input.signal?.addEventListener('abort', abort, { once: true });
const timer = setTimeout(() => {
timedOut = true;
void terminateTree(child);
}, input.deadlineMs);
child.once('error', () => {
clearTimeout(timer);
reject(new PublishRuntimeError('LOCAL_BUILD_RUNTIME_UNAVAILABLE'));
input.signal?.removeEventListener('abort', abort);
if (settled) return;
settled = true;
reject(new PublishRuntimeError(cancelled ? 'LOCAL_BUILD_CANCELLED' : 'LOCAL_BUILD_RUNTIME_UNAVAILABLE'));
});
child.once('close', (code) => {
clearTimeout(timer);
if (timedOut) reject(new PublishRuntimeError('LOCAL_BUILD_TIMEOUT'));
input.signal?.removeEventListener('abort', abort);
if (settled) return;
settled = true;
if (cancelled) reject(new PublishRuntimeError('LOCAL_BUILD_CANCELLED'));
else if (timedOut) reject(new PublishRuntimeError('LOCAL_BUILD_TIMEOUT'));
else if (code !== 0) reject(new PublishRuntimeError('LOCAL_BUILD_FAILED'));
else resolve({ stdout, stderr });
});
});
}
export async function resolvePublishRuntime(): Promise<PublishRuntime> {
export async function resolvePublishRuntime(context?: Partial<PublishRuntimeContext>): Promise<PublishRuntime> {
const isPackaged = context?.isPackaged ?? Boolean(app?.isPackaged);
const resourcesPath = context?.resourcesPath ?? process.resourcesPath;
const cacheKey = `${isPackaged ? 'packaged' : 'development'}:${resourcesPath}`;
const cachedPublishRuntime = cachedPublishRuntimes.get(cacheKey);
if (cachedPublishRuntime) return cachedPublishRuntime;
const nextRuntime = resolvePublishRuntimeUncached({ isPackaged, resourcesPath });
cachedPublishRuntimes.set(cacheKey, nextRuntime);
try {
const packagedNpmPackage = join(process.resourcesPath, 'publish-runtime', 'package.json');
const npmPackage = app.isPackaged && existsSync(packagedNpmPackage)
return await nextRuntime;
} catch (error) {
cachedPublishRuntimes.delete(cacheKey);
throw error;
}
}
async function resolvePublishRuntimeUncached(context: PublishRuntimeContext): Promise<PublishRuntime> {
try {
const packagedNpmPackage = join(context.resourcesPath, 'publish-runtime', 'package.json');
const npmPackage = context.isPackaged && existsSync(packagedNpmPackage)
? packagedNpmPackage
: require.resolve('npm/package.json');
const npmCli = join(dirname(npmPackage), 'bin', 'npm-cli.js');