perf(backend): lazy-load composerize and isomorphic-git (#819)

Both modules are opt-in:
  - composerize (~2 MB) is only used by /api/convert when a user pastes a
    docker run command into the converter UI.
  - isomorphic-git plus isomorphic-git/http/node (~5 MB combined) only fire
    when a stack is created from a Git source.

Previously each was imported at module scope, parsing the whole package on
every cold start regardless of whether the feature was used. Wrap them in
small load-and-cache helpers so the first call resolves the module via
Node's loader and every subsequent call returns the cached reference.

The pattern matches the existing dynamic import of @aws-sdk/client-ecr in
RegistryService. Existing tests using vi.mock('isomorphic-git', ...) and
vi.mock('isomorphic-git/http/node', ...) keep working without changes
because dynamic and static imports share the same module registry.
This commit is contained in:
Anso
2026-04-28 01:37:39 -04:00
committed by GitHub
parent 279ec62dff
commit 329b4ec4e2
2 changed files with 33 additions and 4 deletions
+22 -2
View File
@@ -3,8 +3,6 @@ import { spawn } from 'child_process';
import crypto from 'crypto';
import os from 'os';
import path from 'path';
import git from 'isomorphic-git';
import gitHttp from 'isomorphic-git/http/node';
import YAML from 'yaml';
import { CryptoService } from './CryptoService';
import { DatabaseService, type StackGitSource, type GitSourceAuthType } from './DatabaseService';
@@ -13,6 +11,27 @@ import { ComposeService } from './ComposeService';
import { isDebugEnabled } from '../utils/debug';
import { sanitizeForLog } from '../utils/safeLog';
// isomorphic-git is the heaviest dependency in the backend (~5 MB) and only
// fires when a stack is created from a Git source. Lazy-load it so cold
// boots without any Git-sourced stacks never parse the module.
type IsomorphicGit = typeof import('isomorphic-git')['default'];
type IsomorphicGitHttp = typeof import('isomorphic-git/http/node')['default'];
let cachedGit: IsomorphicGit | undefined;
let cachedGitHttp: IsomorphicGitHttp | undefined;
async function loadIsomorphicGit(): Promise<{ git: IsomorphicGit; gitHttp: IsomorphicGitHttp }> {
if (!cachedGit || !cachedGitHttp) {
const [gitMod, gitHttpMod] = await Promise.all([
import('isomorphic-git'),
import('isomorphic-git/http/node'),
]);
cachedGit = gitMod.default;
cachedGitHttp = gitHttpMod.default;
}
return { git: cachedGit, gitHttp: cachedGitHttp };
}
/**
* GitSourceService - fetch compose files from a Git repository and apply
* them to local stacks. Tokens are encrypted via CryptoService. Shallow
@@ -393,6 +412,7 @@ export class GitSourceService {
: undefined;
try {
const { git, gitHttp } = await loadIsomorphicGit();
// isomorphic-git does not natively accept an AbortSignal, so we
// wrap the clone in a Promise.race against a timeout rejection.
// The clone will keep running in the background until the socket