refactor: cache optional runtime imports

This commit is contained in:
Peter Steinberger
2026-04-18 20:43:11 +01:00
parent 6d40de45c7
commit 0195da6b0e
8 changed files with 93 additions and 21 deletions

View File

@@ -67,19 +67,24 @@ type AzureIdentityModule = {
const AZURE_IDENTITY_MODULE = "@azure/identity";
let azureIdentityModulePromise: Promise<AzureIdentityModule> | null = null;
async function loadAzureIdentity(): Promise<AzureIdentityModule> {
return (await import(AZURE_IDENTITY_MODULE)) as AzureIdentityModule;
azureIdentityModulePromise ??= import(AZURE_IDENTITY_MODULE) as Promise<AzureIdentityModule>;
return azureIdentityModulePromise;
}
let msTeamsSdkPromise: Promise<MSTeamsTeamsSdk> | null = null;
export async function loadMSTeamsSdk(): Promise<MSTeamsTeamsSdk> {
const [appsModule, apiModule] = await Promise.all([
msTeamsSdkPromise ??= Promise.all([
import("@microsoft/teams.apps"),
import("@microsoft/teams.api"),
]);
return {
]).then(([appsModule, apiModule]) => ({
App: appsModule.App,
Client: apiModule.Client,
};
}));
return msTeamsSdkPromise;
}
/**
@@ -653,6 +658,20 @@ const BOT_FRAMEWORK_ISSUERS: ReadonlyArray<{
},
];
type BotFrameworkJwtDeps = {
jwt: typeof import("jsonwebtoken");
JwksClient: typeof import("jwks-rsa").JwksClient;
};
let botFrameworkJwtDepsPromise: Promise<BotFrameworkJwtDeps> | null = null;
async function loadBotFrameworkJwtDeps(): Promise<BotFrameworkJwtDeps> {
botFrameworkJwtDepsPromise ??= Promise.all([import("jsonwebtoken"), import("jwks-rsa")]).then(
([jwt, { JwksClient }]) => ({ jwt, JwksClient }),
);
return botFrameworkJwtDepsPromise;
}
/**
* Create a Bot Framework JWT validator using jsonwebtoken + jwks-rsa directly.
*
@@ -670,8 +689,7 @@ const BOT_FRAMEWORK_ISSUERS: ReadonlyArray<{
export async function createBotFrameworkJwtValidator(creds: MSTeamsCredentials): Promise<{
validate: (authHeader: string) => Promise<boolean>;
}> {
const jwt = await import("jsonwebtoken");
const { JwksClient } = await import("jwks-rsa");
const { jwt, JwksClient } = await loadBotFrameworkJwtDeps();
const allowedAudiences: [string, ...string[]] = [
creds.appId,

View File

@@ -728,10 +728,19 @@ function ffmpegToPCM(
});
}
type MpegDecoderConstructor = typeof import("mpg123-decoder").MPEGDecoder;
let mpegDecoderConstructorPromise: Promise<MpegDecoderConstructor> | null = null;
async function loadMpegDecoderConstructor(): Promise<MpegDecoderConstructor> {
mpegDecoderConstructorPromise ??= import("mpg123-decoder").then(({ MPEGDecoder }) => MPEGDecoder);
return mpegDecoderConstructorPromise;
}
/** Decode MP3 into PCM through mpg123-decoder when ffmpeg is unavailable. */
async function wasmDecodeMp3ToPCM(buf: Buffer, targetRate: number): Promise<Buffer | null> {
try {
const { MPEGDecoder } = await import("mpg123-decoder");
const MPEGDecoder = await loadMpegDecoderConstructor();
debugLog(`[audio-convert] WASM MP3 decode: size=${buf.length} bytes`);
const decoder = new MPEGDecoder();
await decoder.ready;

View File

@@ -219,6 +219,18 @@ async function resolveEnvProxyAgent(
});
}
type UndiciProxyAgentsModule = Pick<typeof import("undici"), "EnvHttpProxyAgent" | "ProxyAgent">;
let undiciProxyAgentsModulePromise: Promise<UndiciProxyAgentsModule> | null = null;
async function loadUndiciProxyAgents(): Promise<UndiciProxyAgentsModule> {
undiciProxyAgentsModulePromise ??= import("undici").then(({ EnvHttpProxyAgent, ProxyAgent }) => ({
EnvHttpProxyAgent,
ProxyAgent,
}));
return undiciProxyAgentsModulePromise;
}
async function resolveEnvFetchDispatcher(
logger: ReturnType<typeof getChildLogger>,
agent?: unknown,
@@ -229,7 +241,7 @@ async function resolveEnvFetchDispatcher(
return undefined;
}
try {
const { EnvHttpProxyAgent, ProxyAgent } = await import("undici");
const { EnvHttpProxyAgent, ProxyAgent } = await loadUndiciProxyAgents();
return proxyUrl
? new ProxyAgent({ allowH2: false, uri: proxyUrl })
: new EnvHttpProxyAgent({ allowH2: false });

View File

@@ -136,6 +136,13 @@ function installCiaoConsoleNoiseFilter(): () => void {
};
}
let ciaoModulePromise: Promise<typeof import("@homebridge/ciao")> | null = null;
async function loadCiaoModule(): Promise<typeof import("@homebridge/ciao")> {
ciaoModulePromise ??= import("@homebridge/ciao");
return ciaoModulePromise;
}
export async function startGatewayBonjourAdvertiser(
opts: GatewayBonjourAdvertiseOpts,
): Promise<GatewayBonjourAdvertiser> {
@@ -143,7 +150,7 @@ export async function startGatewayBonjourAdvertiser(
return { stop: async () => {} };
}
const { getResponder, Protocol } = await import("@homebridge/ciao");
const { getResponder, Protocol } = await loadCiaoModule();
const restoreConsoleLog = installCiaoConsoleNoiseFilter();
try {
// mDNS service instance names are single DNS labels; dots in hostnames (like

View File

@@ -4,6 +4,7 @@ import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
import { runExec } from "../process/exec.js";
type Sharp = typeof import("sharp");
type SharpFactory = (buffer: Buffer) => ReturnType<Sharp>;
export type ImageMetadata = {
width: number;
@@ -31,14 +32,18 @@ function prefersSips(): boolean {
);
}
async function loadSharp(): Promise<(buffer: Buffer) => ReturnType<Sharp>> {
const mod = (await import("sharp")) as unknown as { default?: Sharp };
const sharp = mod.default ?? (mod as unknown as Sharp);
return (buffer) =>
sharp(buffer, {
failOnError: false,
limitInputPixels: MAX_IMAGE_INPUT_PIXELS,
});
let sharpFactoryPromise: Promise<SharpFactory> | null = null;
async function loadSharp(): Promise<SharpFactory> {
sharpFactoryPromise ??= import("sharp").then((mod) => {
const sharp = (mod as unknown as { default?: Sharp }).default ?? (mod as unknown as Sharp);
return (buffer: Buffer) =>
sharp(buffer, {
failOnError: false,
limitInputPixels: MAX_IMAGE_INPUT_PIXELS,
});
});
return sharpFactoryPromise;
}
function isPositiveImageDimension(value: number): boolean {

View File

@@ -2,12 +2,19 @@ import type { DatabaseSync } from "node:sqlite";
import { formatErrorMessage } from "../../infra/errors.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
let sqliteVecModulePromise: Promise<typeof import("sqlite-vec")> | null = null;
async function loadSqliteVecModule(): Promise<typeof import("sqlite-vec")> {
sqliteVecModulePromise ??= import("sqlite-vec");
return sqliteVecModulePromise;
}
export async function loadSqliteVecExtension(params: {
db: DatabaseSync;
extensionPath?: string;
}): Promise<{ ok: boolean; extensionPath?: string; error?: string }> {
try {
const sqliteVec = await import("sqlite-vec");
const sqliteVec = await loadSqliteVecModule();
const resolvedPath = normalizeOptionalString(params.extensionPath);
const extensionPath = resolvedPath ?? sqliteVec.getLoadablePath();

View File

@@ -134,6 +134,13 @@ export function createDeferred<T>() {
return { promise, resolve, reject };
}
let proxyAgentConstructorPromise: Promise<typeof import("proxy-agent").ProxyAgent> | null = null;
async function loadProxyAgentConstructor(): Promise<typeof import("proxy-agent").ProxyAgent> {
proxyAgentConstructorPromise ??= import("proxy-agent").then(({ ProxyAgent }) => ProxyAgent);
return proxyAgentConstructorPromise;
}
export async function resolveAmbientNodeProxyAgent<TAgent>(params?: {
onError?: (error: unknown) => void;
onUsingProxy?: () => void;
@@ -143,7 +150,7 @@ export async function resolveAmbientNodeProxyAgent<TAgent>(params?: {
return undefined;
}
try {
const { ProxyAgent } = await import("proxy-agent");
const ProxyAgent = await loadProxyAgentConstructor();
params?.onUsingProxy?.();
return new ProxyAgent() as TAgent;
} catch (error) {

View File

@@ -34,6 +34,13 @@ type PtyModule = {
export type PtyAdapter = SpawnProcessAdapter;
let ptyModulePromise: Promise<PtyModule> | null = null;
async function loadPtyModule(): Promise<PtyModule> {
ptyModulePromise ??= import("@lydell/node-pty") as Promise<unknown> as Promise<PtyModule>;
return ptyModulePromise;
}
export async function createPtyAdapter(params: {
shell: string;
args: string[];
@@ -43,7 +50,7 @@ export async function createPtyAdapter(params: {
rows?: number;
name?: string;
}): Promise<PtyAdapter> {
const module = (await import("@lydell/node-pty")) as unknown as PtyModule;
const module = await loadPtyModule();
const spawn = module.spawn ?? module.default?.spawn;
if (!spawn) {
throw new Error("PTY support is unavailable (node-pty spawn not found).");