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
+11 -2
View File
@@ -1,11 +1,19 @@
import { Router, type Request, type Response } from 'express';
// @ts-ignore - composerize lacks proper type definitions
import composerize from 'composerize';
import { authMiddleware } from '../middleware/auth';
import { sanitizeForLog } from '../utils/safeLog';
const MAX_DOCKER_RUN_LENGTH = 8192;
// composerize is only used when a user pastes a `docker run` command into the
// converter UI. Lazy-load it so cold boot does not parse the ~2 MB module.
let cachedComposerize: ((dockerRun: string) => string) | undefined;
async function loadComposerize(): Promise<(dockerRun: string) => string> {
if (!cachedComposerize) {
cachedComposerize = (await import('composerize')).default;
}
return cachedComposerize;
}
export const convertRouter = Router();
convertRouter.post('/', authMiddleware, async (req: Request, res: Response): Promise<void> => {
@@ -30,6 +38,7 @@ convertRouter.post('/', authMiddleware, async (req: Request, res: Response): Pro
let yaml: unknown;
try {
const composerize = await loadComposerize();
yaml = composerize(trimmed);
} catch (error) {
console.error('Conversion error:', error);