asyncapi / asyncapi/cli

[Proposal] Improve CLI startup speed by lazy-loading heavy services and dependencies

Open
#2,269 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
272
Forks
375
Avg merge
3h 22m
Merged PRs (30d)
8

Description

## The Problem
When running CLI commands (especially simple ones like `asyncapi --help`, `asyncapi --version`, or `asyncapi config`), there is a noticeable delay before the output appears on the terminal.

When profiling module load times in the built codebase (`lib/`), Node.js spends significant time loading heavy dependencies upfront before the CLI even determines which command to run:

- `require('@asyncapi/parser')` takes **~550 ms**.
- `require('lib/apps/cli/internal/base')` takes **~670 ms**.
- `require('lib/domains/services/validation.service')` takes **~1,000 ms**.
- Running a topic command like `asyncapi config` takes **~3.4 seconds**.

**NOTE**: These benchmarks are based on my machine so they may not be universal for everyone.

### How were these numbers measured? (Reproducible Benchmarks)

1. **CLI (`time` command):**
```bash
# Build the lib/ output first
npm run build

# Measure execution time of a topic command
time ./bin/run_bin config
```
**Output:**
```text
real 0m3.401s
user 0m1.060s
sys 0m0.235s
```
---

## Why is this happening?

Looking at the code, a few heavy modules are imported and instantiated at the top level when files are loaded:

1. **`src/apps/cli/internal/base.ts`**
- Imports `@asyncapi/parser` at the top level and creates `parser = new Parser();` on class definition. Since `base.ts` is the base class for commands, this runs every time commands are indexed or loaded.
2. **Command classes (e.g. `src/apps/cli/commands/validate.ts`, `convert.ts`)**
- Classes create service instances (like `private validationService = new ValidationService();`) directly as class properties. When OCLIF loads the command module, these fields are evaluated right away.
3. **`src/domains/services/validation.service.ts`**
- Top-level imports bring in several schema parsers (Avro, OpenAPI, RAML, Protobuf) and formatters all at once.

Because of this, running basic commands like help or topic loads heavy modules unnecessarily.

---

## Proposed Solution

Instead of loading services at top-level, we can defer loading them until the command's `run()` method actually executes, or use lazy getters.

### Example Changes

1. **In `src/apps/cli/internal/base.ts`:**
Convert the `parser` property to a lazy getter so `@asyncapi/parser` is only required when `this.parser` is accessed:

```typescript
private _parser?: any;

get parser() {
if (!this._parser) {
const { Parser } = require('@asyncapi/parser');
this._parser = new Parser();
}
return this._parser;
}
```

2. **In Command Files (e.g. `validate.ts`, `convert.ts`):**
Move service instantiation out of class field declarations into `run()` or a lazy getter:

```typescript
private _validationService?: ValidationService;

private get validationService(): ValidationService {
if (!this._validationService) {
const { ValidationService } = require('@services/validation.service');
this._validationService = new ValidationService();
}
return this._validationService;
}
```

3. **In Services (`validation.service.ts`):**
Defer loading secondary schema parsers (Avro, Protobuf, RAML) until they are required explicitly.

---

## Expected Results

- **Faster startup for simple commands:** Commands like `asyncapi config`, `--help`, or `--version` can run much faster.
- **Better shell completion:** Keeps tab-completion responsive when developers type `asyncapi `.

---

## Questions for Maintainers

1. Does this lazy-loading approach align with how maintainers want to enhance the performance of the cli?
2. Are there specific commands or services where maintainers prefer keeping standard imports vs lazy loading?
3. Are there any other things regarding this issue, which maintainers want to be addressed or solved in a different way?

## Are you willing to work on this issue?

Yes, I'm willing to submit a PR.

Contributor guide

Open the contributing guide

Research direction

Build the lib/ output with npm run build, then benchmark ./bin/run_bin config and the simple --help and --version commands. Read src/apps/cli/internal/base.ts, the validate.ts and convert.ts command files, and src/domains/services/validation.service.ts to trace top-level imports and service construction. Done means heavy services load only when needed while command behavior is unchanged and startup measurements improve.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
cli, performance
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.