Основные концепции
API расширений Diplodoc построено вокруг нескольких ключевых концепций, которые работают вместе, чтобы обеспечить гибкую и мощную систему расширений.
Интерфейс расширения
Любое расширение должно реализовывать интерфейс IExtension
:
interface IExtension<Program extends BaseProgram = BaseProgram> {
apply(program: Program): void;
}
Каждый extension module должен экспортировать class с именем Extension
. Система будет искать именно это имя класса при загрузке extensions.
// ✅ Правильно - имя класса 'Extension'
export class Extension {
apply(program: Build) {
// Extension logic
}
}
// ❌ Неправильно - другое имя класса
export class MyCustomExtension {
apply(program: Build) {
// Этот class не будет загружен системой
}
}
Инициализация расширения
-
Каждое расширение должно иметь метод
apply
-
Через метод
apply
расширение получает доступ к экземпляру программы. -
Метод
apply
вызывается при инициализации каждой команды- Например, если у вас есть команды
build
,translate
,publish
, тоapply
будет вызван три раза, - Дополнительно
apply
вызывается при инициализации корневой программы.
- Например, если у вас есть команды
-
Получив доступ к программе, расширение может подписаться на ее хуки.
-
Попытка подписаться на хуки другой программы будет проигнорирована. Это позволяет подписываться на хуки программы, не задумываясь о том, с какой именно программой вы работаете в данный момент.
Например, если расширение вызывается для программы
translate
, тоgetBuildHooks(program)
вернет набор хуков, который никогда не будет вызван -
Расширение не может быть уверено, с какой именно командой оно работает в данный момент. Это накладывает определенные ограничения на логику работы расширения.
-
Не рекомендуется сохранять состояние между вызовами
apply
Основные принципы работы с хуками
Типы хуков
В Diplodoc используются следующие типы хуков:
- SyncHook - синхронный хук, вызывается последовательно
- AsyncSeriesHook - асинхронный хук, вызывается последовательно
- AsyncParallelHook - асинхронный хук, вызывается параллельно
- AsyncSeriesWaterfallHook - асинхронный хук, где результат предыдущего обработчика передается следующему
Подписка на хуки
Для подписки на хуки используются методы:
tap
- для синхронных хуковtapPromise
- для асинхронных хуковtapAsync
- для асинхронных хуков с callback
// Подписка на синхронный хук
hooks.Command.tap('MyExtension', (command) => {
// Обработка команды
});
// Подписка на асинхронный хук
hooks.BeforeAnyRun.tapPromise('MyExtension', async (run) => {
// Асинхронная обработка
});
// Подписка на waterfall хук
hooks.Config.tapPromise('MyExtension', async (config) => {
// Модификация конфигурации
return config;
});
Порядок выполнения
- Хуки выполняются в порядке их регистрации
- Для AsyncSeriesHook и AsyncSeriesWaterfallHook обработчики выполняются последовательно
- Для AsyncParallelHook обработчики выполняются параллельно
- Для AsyncSeriesWaterfallHook результат предыдущего обработчика передается следующему
Обработка ошибок
- Синхронные хуки: ошибки обрабатываются через try/catch
- Асинхронные хуки: ошибки обрабатываются через Promise.catch или try/catch в async функциях
// Обработка ошибок в асинхронном хуке
hooks.BeforeAnyRun.tapPromise('MyExtension', async (run) => {
try {
// Логика расширения
} catch (error) {
run.logger.error('Extension error:', error);
throw new HandledError('Extension failed');
}
});
Лучшие практики
- Всегда указывайте уникальное имя для обработчика хука
- Используйте правильный тип хука в зависимости от задачи
- Обрабатывайте ошибки в асинхронных хуках
- Не блокируйте выполнение в синхронных хуках
- Для модификации данных используйте AsyncSeriesWaterfallHook
Архитектура программы
BaseProgram
Класс BaseProgram
является основой CLI Diplodoc:
export class BaseProgram<TConfig extends BaseConfig = BaseConfig, TArgs extends BaseArgs = BaseArgs> {
readonly name: string;
readonly command: Command;
readonly config: Config<TConfig>;
readonly logger: Logger;
readonly options: ExtendedOption[];
protected modules: ICallable[];
protected extensions: (string | ExtensionInfo)[];
}
Ключевые компоненты:
- name: Уникальный идентификатор программы
- command: Экземпляр CLI команды
- config: Конфигурация программы
- logger: Система логирования
- options: Опции командной строки
- modules: Список расширений и подпрограмм
- extensions: Конфигурации расширений
Система хуков
Система хуков основана на tapable и предоставляет несколько типов хуков:
export function hooks<TRun extends Run, TConfig extends BaseConfig, TArgs extends BaseArgs>(name: string) {
return {
Command: new SyncHook<[Command, ExtendedOption[]]>(),
RawConfig: new AsyncSeriesHook<[DeepFrozen<TConfig>, TArgs]>(),
Config: new AsyncSeriesWaterfallHook<[TConfig, TArgs]>(),
BeforeAnyRun: new AsyncSeriesHook<[TRun]>(),
AfterAnyRun: new AsyncSeriesHook<[TRun]>()
};
}
Система конфигурации
Расширения могут быть настроены двумя способами:
1. Конфигурация через файл
{
"extensions": [
{
"path": "./my-extension",
"options": {
"setting1": "value1",
"setting2": "value2"
}
}
]
}
2. Программная конфигурация
class Build extends BaseProgram {
readonly modules = [
new MyExtension({
setting1: "value1",
setting2: "value2"
})
];
}
Контекст выполнения
Класс Run
предоставляет контекст для обработки документов:
export class Run extends BaseRun<BuildConfig> {
readonly vars: VarsService;
readonly meta: MetaService;
readonly toc: TocService;
readonly vcs: VcsService;
readonly leading: LeadingService;
readonly markdown: MarkdownService;
readonly search: SearchService;
readonly logger: Logger;
}