mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-28 11:17:07 +00:00
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:
@@ -1,11 +1,19 @@
|
|||||||
import { Router, type Request, type Response } from 'express';
|
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 { authMiddleware } from '../middleware/auth';
|
||||||
import { sanitizeForLog } from '../utils/safeLog';
|
import { sanitizeForLog } from '../utils/safeLog';
|
||||||
|
|
||||||
const MAX_DOCKER_RUN_LENGTH = 8192;
|
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();
|
export const convertRouter = Router();
|
||||||
|
|
||||||
convertRouter.post('/', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
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;
|
let yaml: unknown;
|
||||||
try {
|
try {
|
||||||
|
const composerize = await loadComposerize();
|
||||||
yaml = composerize(trimmed);
|
yaml = composerize(trimmed);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Conversion error:', error);
|
console.error('Conversion error:', error);
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { spawn } from 'child_process';
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import git from 'isomorphic-git';
|
|
||||||
import gitHttp from 'isomorphic-git/http/node';
|
|
||||||
import YAML from 'yaml';
|
import YAML from 'yaml';
|
||||||
import { CryptoService } from './CryptoService';
|
import { CryptoService } from './CryptoService';
|
||||||
import { DatabaseService, type StackGitSource, type GitSourceAuthType } from './DatabaseService';
|
import { DatabaseService, type StackGitSource, type GitSourceAuthType } from './DatabaseService';
|
||||||
@@ -13,6 +11,27 @@ import { ComposeService } from './ComposeService';
|
|||||||
import { isDebugEnabled } from '../utils/debug';
|
import { isDebugEnabled } from '../utils/debug';
|
||||||
import { sanitizeForLog } from '../utils/safeLog';
|
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
|
* GitSourceService - fetch compose files from a Git repository and apply
|
||||||
* them to local stacks. Tokens are encrypted via CryptoService. Shallow
|
* them to local stacks. Tokens are encrypted via CryptoService. Shallow
|
||||||
@@ -393,6 +412,7 @@ export class GitSourceService {
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const { git, gitHttp } = await loadIsomorphicGit();
|
||||||
// isomorphic-git does not natively accept an AbortSignal, so we
|
// isomorphic-git does not natively accept an AbortSignal, so we
|
||||||
// wrap the clone in a Promise.race against a timeout rejection.
|
// wrap the clone in a Promise.race against a timeout rejection.
|
||||||
// The clone will keep running in the background until the socket
|
// The clone will keep running in the background until the socket
|
||||||
|
|||||||
Reference in New Issue
Block a user