@emailmaker/ui-kit
v1.1.84
Published
UI Kit for Emailmaker
Readme
UI Kit
Набор UI-компонентов и темы для plugin-system.
Источник публичного README для npm: этот каталог (PACKAGE_CONFIG['ui-kit'].docsPath). Сборка: build/ui-kit/README.md.
Публичная документация:
📦 Система плагинов для emailmaker
Библиотека предоставляет расширяемую архитектуру для создания плагинов для визуального редактора писем emailmaker. Плагины позволяют:
- расширять функциональность редактора;
- разрабатывать собственные UI-компоненты с использованием экспортированных версий React и Ant Design
- взаимодействовать с кодом письма в iframe;
- отображать модальные окна на основе Ant Design в редакторе;
Разработка осуществляется с полной поддержкой TypeScript, что обеспечивает строгость типов, модульность и предсказуемость.
⚙️ Требования
- Современный браузер с поддержкой ES2015 (и выше)
- Node.js >= 20 (рекомендуется LTS; CLI-шаблон использует
tsxи Vite/Webpack 5) - React и Ant Design в плагине подключаются через
PluginDev(alias/externals на runtime хоста из@emailmaker/emailmaker/runtime/*), а не отдельным npm-пакетом runtime
📘 Термины
- sandbox — часть плагина, работающая внутри iframe, для взаимодействия с содержимым письма.
- app — часть плагина, встроенная в редактор. Отвечает за интерфейс.
- pluginRegistry — механизм регистрации плагинов.
- externals — способ подключения внешних библиотек в webpack.
🔰 Быстрый старт
👉 Рекомендуемый способ — CLI (@emailmaker/cli): согласованная структура, Vite/Webpack, app + sandbox, dev/release host.
npx @emailmaker/cli my-plugin --kind plugin --preset advanced --bundler vite --target npm-package --output-dir ./my-plugin
cd my-plugin
npm install
npm run startСгенерированный проект включает:
- Webpack / Vite (по выбору)
- React + Ant Design
- TypeScript
- Структуру проекта, описанную ниже
Проверка release-сборки без ручного деплоя:
npm run build
npm run start:releaseАльтернатива: пример из репозитория
Готовый example можно клонировать для ознакомления; для новых плагинов предпочтительнее CLI-шаблон.
git clone https://github.com/emailmaker
cd simple_plugin
npm install
npm run start🛠 Установка
📦 Основные пакеты
npm install @emailmaker/emailmaker @emailmaker/extensions-app @emailmaker/extensions-react @emailmaker/extensions-sandbox| Пакет | Назначение | Что экспортирует |
|-------|------------|------------------|
| @emailmaker/emailmaker | Основной пакет | init(), prefetch(), типы IPlugin, Instance; runtime React/Ant Design — subpath @emailmaker/emailmaker/runtime/* (отдельный npm не нужен) |
| @emailmaker/extensions-app | API для app-плагинов | pluginRegistry, DomComponentRegistry, ElementsApi, SettingsPanelApi, ModalApi |
| @emailmaker/extensions-react | React API для плагинов | ComponentRegistry, createComponentIdentifier() |
| @emailmaker/extensions-sandbox | API для sandbox-плагинов | App, MessageService, SyncService |
@emailmaker/ui-kit ставится отдельно (devDependency в CLI-шаблоне), если плагин использует готовые UI-компоненты платформы.
React и Ant Design в коде плагина импортируются как
react/antd.PluginDevв dev/release подставляет runtime хоста через alias/externals — см. раздел «Сборка плагина».
Импорты
// React и Antd — обычные импорты
import React from 'react';
import { Button } from 'antd';
// UI-компоненты платформы (опционально)
import { ColorPicker } from '@emailmaker/ui-kit';
// Core API плагинов
import { pluginRegistry, ElementsApi } from '@emailmaker/extensions-app';
// React-пакет для плагинов
import { createComponentIdentifier } from '@emailmaker/extensions-app';
import { ComponentRegistry } from '@emailmaker/extensions-react';
// Типы из основного пакета
import type { IPlugin, Instance } from '@emailmaker/emailmaker';Что обычно ставить
- если у вас обычный React-плагин, ставьте все пакеты из команды выше
- если используете UI Kit, добавьте ещё
@emailmaker/ui-kit - если пишете только sandbox-часть, достаточно main +
@emailmaker/extensions-sandbox
Для React-плагинов пакет
@emailmaker/extensions-reactнужен по умолчанию.
🔧 Сборка плагина
Используйте PluginDev для настройки бандлера. Он делает две вещи:
- alias — настраивает React и Ant Design для работы в плагине
- externals — выносит общие зависимости из бандла
npm install @emailmaker/emailmakerТаргеты для автора плагина
| Таргет | Что получится | Внутри использует |
|--------|----------------|-------------------|
| npm-пакет | ESM bundle для package-based интеграции | 'esm' |
| Встраиваемый bundle | UMD/script-подключение в host | 'globals' |
| Advanced | Webpack-only async globals compatibility | 'async-globals' / 'legacy-async-globals' |
PluginDev({ externals: false }) используется только для local dev host и не считается release target.
Типичный проект: serve + build
Самый частый сценарий — плагин в отдельном проекте с двумя режимами:
| Режим | Entry | Externals | Основной плагин |
|-------|-------|-----------|-----------------|
| serve | src/dev.ts | false | Да (статика, iframe) |
| build | src/index.ts | зависит от выбранного target | Нет |
// src/dev.ts — тестовая страница с основным приложением
import '@emailmaker/emailmaker';
import { MyPlugin } from './index';
emailmaker.init({ plugins: [MyPlugin] });Vite:
import VitePlugin from '@emailmaker/emailmaker/vite';
import PluginDev from '@emailmaker/emailmaker/vite/pluginDev';
export default defineConfig(({ command }) => ({
plugins: [
command === 'serve' && VitePlugin(),
PluginDev({ externals: command === 'build' ? 'esm' : false }),
],
build: {
lib: { entry: 'src/index.ts', formats: ['es'] },
},
}));Webpack:
const WebpackPlugin = require('@emailmaker/emailmaker/webpack');
const PluginDev = require('@emailmaker/emailmaker/webpack/pluginDev');
const isDev = process.env.NODE_ENV === 'development';
module.exports = {
entry: isDev ? './src/dev.ts' : './src/index.ts',
plugins: [
isDev && new WebpackPlugin(),
new PluginDev({ externals: isDev ? false : 'esm' }),
].filter(Boolean),
};
VitePlugin/WebpackPlugin— основной плагин для хост-приложений. В serve обслуживает iframe, шрифты и статику. В build — копирует их в output.
Опции PluginDev
| Опция | По умолчанию | Значения |
|-------|-------------|----------|
| externals | 'esm' | 'esm', 'globals', 'async-globals' (Webpack), false |
| alias | mode-aware | true / false |
Для большинства внешних плагинов не выбирайте externals вручную — используйте CLI target:
npm-пакет->esmвстраиваемый bundle->globalsadvanced-> только если host уже требует async/globals контракт Webpack
Для встраиваемый bundle и advanced рекомендуемый runtime-контракт — descriptor вида { type: 'umd', url, name, resolve: 'registry' }, где name совпадает с ключом из PluginTypeMap.
Ручная настройка (без PluginDev)
Нужна только для продвинутой интеграции или собственного бандлерного пайплайна. Основной сценарий для внешнего плагина — отдельный пакет с PluginDev.
resolve.alias:react→@emailmaker/emailmaker/runtime/react(и т.д.) дляesm/ dev; локальные shared internals идут через compat facades runtime; UI-kit (@emailmaker/ui-kit) →@emailmaker/emailmaker/ui-kit(singleton bytes на main, неruntime/@scope/ui-kit)externals:@emailmaker/emailmaker/runtime/core, runtime npm-модули; ui-kit external →@emailmaker/emailmaker/ui-kit(глобальный ключUiKitвruntimeExports, см. манифест)- Список модулей:
@emailmaker/emailmaker/runtime/plugin-dev-manifest.json→runtimeModules
Если release bundle неожиданно стал толстым или target встраиваемый bundle не загружается, начните с раздела Troubleshooting.
Типы для плагина всё равно собираются отдельно: CLI-шаблон запускает build:types и кладёт dist/public-types.d.ts рядом с JS bundle, поэтому TypeScript support сохраняется и для browser/advanced targets.
tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx"
}
}Что писать в коде
В коде плагина пишите обычные импорты:
import React from 'react';
import { Button } from 'antd';А React API для плагинов берите из @emailmaker/extensions-react:
import { createComponentIdentifier } from '@emailmaker/extensions-app';
import { ComponentRegistry } from '@emailmaker/extensions-react';Обычно достаточно использовать готовый шаблон CLI и не настраивать alias/external вручную.
📂 PublicPath
publicPath используется для указания базового URL для загрузки второстепенных скриптов и статики плагина.
Более подробно прочитать про publicPath можно в секции publicPath документации webpack
Внутри плагина:
export interface MyPluginOptions {
publicPath?: string;
}
class MyPlugin implements IPlugin {
constructor(
private editor: Instance,
private options: MyPluginOptions = {}
) {}
init() {
if (this.options.publicPath) {
const sandboxApi = this.editor.use('SandboxScriptApi');
sandboxApi.registerSandboxScript(this.options.publicPath + 'sandbox.js');
}
}
}Подключение плагина с publicPath:
emailmaker.init({
plugins: [
['MyPlugin', { publicPath: 'https://cdn.example.com/my-plugin/' }]
]
});📁 Структура проекта
CLI-шаблон (типичный layout):
my-plugin/
├── src/
│ ├── app/ # UI-часть, взаимодействие с редактором
│ │ └── MyPlugin.tsx
│ ├── sandbox/ # Скрипт для DOM письма (отдельная сборка)
│ │ └── index.ts
│ ├── dev.tsx # Локальный debug host (редактор + плагин)
│ └── public-types.d.ts # Типы плагина для npm / augmentation
├── config/ # Vite/Webpack: dev host, release, sandbox
├── scripts/ # dev-runner, build-runner, release-runner, build-types
├── dist/ # Сборка app-плагина (+ public-types.d.ts)
└── package.jsonsrc/app— регистрация плагина, панели, работа с API редактора. Отсюда подключается sandbox:
const sandboxApi = this.editor.use('SandboxScriptApi');
sandboxApi.registerSandboxScript(this.options.publicPath + 'sandbox.js');src/sandbox— код в iframe; собирается отдельным entry (build:sandbox), без React.src/dev.tsx— локальный запускemailmakerс плагином (npm run start).src/public-types.d.ts—PluginTypeMap,Config, опции; послеnpm run build:typesкопируется вdist/public-types.d.ts.
Общие идентификаторы событий app ↔ sandbox держите в src/interfaces/ (или аналогичном shared-модуле внутри src/).
🧩 Жизненный цикл плагина
Плагин может быть реализован в виде класса или фабрики. В конструкторе класс получает экземпляр редактора emailmaker и должен соответствовать интерфейсу:
export interface IPlugin {
required?(): void;
init?(): Promise<void> | void;
afterInit?(): Promise<void> | void;
dispose?(): Promise<void> | void;
}| Метод | Назначение |
|---------------|------------|
| required() | Указание зависимостей. Не обязательная, можно в init, но в сложных проектах могут быть циклические ссылки |
| init() | Инициализация плагина. Подключение других плагинов, инициализация ресурсов |
| afterInit() | Методы, вызываемые после инициализации всех плагинов |
| dispose() | Очистка ресурсов, отписка от событий |
Пример плагина
import type { IPlugin, Instance } from '@emailmaker/emailmaker';
class TestPlugin implements IPlugin {
constructor(readonly _editor: Instance) {}
async init() {
try {
const sandboxScriptApi = this._editor.use('SandboxScriptApi');
// ...
} catch (e) {
console.error("Ошибка инициализации:", e);
}
}
dispose() {
// Очистка ресурсов
}
}Sandbox-часть
Скрипт в iframe (@emailmaker/extensions-sandbox) использует упрощённый lifecycle — без required():
import { App } from '@emailmaker/extensions-sandbox';
import type { IPlugin } from '@emailmaker/extensions-sandbox';
class MyPluginSandbox implements IPlugin {
init() { /* ... */ }
afterInit?() { /* ... */ }
dispose() { /* ... */ }
}
App.registerPlugin('MyPluginSandbox', MyPluginSandbox);🔌 Подключение плагина
1. Прямой класс (рекомендуемый)
Самый простой способ — передать класс плагина напрямую:
import { MyPlugin } from 'my-plugin-package';
emailmaker.init({
plugins: [MyPlugin]
});
// С опциями (second argument)
emailmaker.init({
plugins: [
[MyPlugin, { publicPath: 'https://cdn.example.com/my-plugin/' }]
]
});Конструктор плагина (класс или фабрика) принимает (context, options?):
class MyPlugin implements IPlugin {
constructor(private ctx: Instance, private options?: MyOptions) {}
}
// Или фабрика
function myPlugin(ctx: Instance, options?: MyOptions): IPlugin {
return { init() { /* ... */ } };
}2. Ленивая загрузка
Плагин загружается асинхронно — удобно для code-splitting:
emailmaker.init({
plugins: [
() => import('my-plugin-package').then(m => m.MyPlugin)
]
});Результат — обычный Promise, пользователь сам выбирает нужный экспорт.
3. Декларативная загрузка по URL
Загрузка плагина по URL без явного импорта:
emailmaker.init({
plugins: [
{ type: 'esm', url: 'https://cdn.example.com/my-plugin.js' },
{ type: 'umd', url: 'https://cdn.example.com/my-plugin.umd.js', name: 'MyPlugin', resolve: 'registry' },
]
});| Параметр | Описание |
|----------|----------|
| type | 'esm' — ESM модуль (import()), 'umd' — UMD/IIFE (<script>) |
| url | URL до скрипта плагина |
| name | Для resolve: 'registry' — строковый ключ из PluginTypeMap; для legacy umd/window — ключ в window |
| resolve | Опционально. 'module' (default ESM), 'window' (legacy default UMD), 'registry' |
4. Регистрация по имени (связь между плагинами)
Если плагин A должен обращаться к плагину B через editor.use('PluginB') — используйте pluginRegistry.add:
// В пакете плагина
import { pluginRegistry } from '@emailmaker/extensions-app';
import { MyPlugin } from './MyPlugin';
pluginRegistry.add('MyPlugin', MyPlugin);// В приложении
emailmaker.init({
plugins: ['MyPlugin']
});// В другом плагине
const myPlugin = this.editor.use('MyPlugin');⚠️ Для URL-режимов
globals/async-globalsиспользуйтеresolve: 'registry'иpluginRegistry.add(...). Это делаетnameпроверяемым черезPluginTypeMapи убирает зависимость отwindow[name].
Метод use()
Синхронный метод для получения экземпляра уже загруженного плагина:
// По классу (рекомендуемый — полная типизация)
const myPlugin = this.editor.use(MyPlugin);
// По имени (требует PluginTypeMap и pluginRegistry.add)
const myPlugin = this.editor.use('MyPlugin');Метод useAsync()
Асинхронный метод для работы с ленивыми загрузчиками и дескрипторами:
const myPlugin = await this.editor.useAsync(
() => import('my-plugin').then(m => m.MyPlugin)
);
const myPlugin = await this.editor.useAsync(
{ type: 'esm', url: '/plugins/analytics.js' }
);
const myPluginByKey = await this.editor.useAsync(
{ type: 'umd', url: '/plugins/my-plugin.umd.js', name: 'MyPlugin', resolve: 'registry' }
);📤 API: работа с редактором
ElementsApi
Добавление элемента в боковую панель. Также имеется возможность изменить текущие компоненты или удалить их.
const elementsApi = editor.use('ElementsApi');
elementsApi.insert({
title: "Product",
html: "<div>...</div>",
name: "product-block",
icon: { type: 'IconSun', props: {} }
});SettingsPanelApi
Добавление новой панели настроек. Например, можно добавить панель которая будет отображаться при клике на элементе в sandbox.
import { createComponentIdentifier } from '@emailmaker/extensions-app';
const SettingsPanelId = createComponentIdentifier('MySettingsPanel');
// ComponentRegistry.add(SettingsPanelId, MySettingsPanel);
const panel = editor.use('SettingsPanelApi');
panel.showSettingsPanel({
content: {
type: SettingsPanelId,
props: {...}
},
caption: 'Настройки',
deletable: true
});instanceKey (идентификатор экземпляра панели)
По умолчанию каждый вызов showSettingsPanel получает новый instanceKey, и хост перемонтирует React-компонент панели. Это нужно при переключении между несколькими экземплярами одного блока в письме (две карточки товара с одним ComponentId).
Передайте стабильный instanceKey только если нужно обновлять props без remount и сохранять внутреннее состояние панели (скролл, фокус, черновик формы):
panel.showSettingsPanel({
instanceKey: blockUuid,
content: { type: SettingsPanelId, props: { blockUuid, settings } },
});Не вызывайте closeSettingsPanel перед showSettingsPanel при переключении между экземплярами — достаточно одного showSettingsPanel с новыми props.
Примерная последовательность процесса взаимодействия sandbox и app:
- Обработчик клика в sandbox
- Отправка события в app через MessageService
- Отображение зарегистрированной панели
- Отправки изменений из панели в sandbox через MessageService
ModalApi
Интерфейс основан на модалках
Ant Design
import { createComponentIdentifier } from '@emailmaker/extensions-app';
import { ComponentRegistry } from '@emailmaker/extensions-react';
const MyPanelId = createComponentIdentifier('MyPanel');
ComponentRegistry.add(MyPanelId, MyPanel);
const modal = editor.use('ModalApi');
modal.show({
title: 'Выбор группы',
content: {
type: MyPanelId,
props: { groups, settings },
},
});Для простого UI можно передать JSX напрямую (см. раздел extensions-react).
EmailSettingsApi
API для работы с настройками письма (редактор email / emailmaker). В widget-сценариях может быть недоступен.
Позволяет получать и изменять настройки внешнего вида письма и стили элементов контента.
API разделен на два типа методов:
- Layout Settings — настройки письма целиком (фон, ширина, адаптивность)
- Content Styles — стили элементов контента (текст, заголовки, ссылки, кнопки, блоки, карточки)
Получение и установка настроек внешнего вида письма:
const emailSettingsApi = editor.use('EmailSettingsApi');
// Получить настройки внешнего вида письма
const layoutSettings = await emailSettingsApi.getEmailLayoutSettings();
console.log(layoutSettings.backgroundColor); // цвет фона письма
console.log(layoutSettings.width); // ширина письма {value: 600, dim: 'px'}
console.log(layoutSettings.responsive); // настройки адаптивности
// Изменить настройки внешнего вида письма
await emailSettingsApi.setEmailLayoutSettings({
backgroundColor: '#ffffff',
width: { value: 600, dim: 'px' },
responsive: { shutdown: false, styles: true }
});Получение и установка стилей элементов контента:
// Получить стили элементов контента
const contentStyles = await emailSettingsApi.getEmailContentStyles();
console.log(contentStyles.text); // стили текста
console.log(contentStyles.buttons); // стили кнопок
console.log(contentStyles.block); // стили блоков
// Изменить стили элементов контента
await emailSettingsApi.setEmailContentStyles({
text: { fontSize: 18, color: '#333333' },
buttons: {
backgroundColor: '#1890ff',
borderRadius: { all: 8 }
},
block: {
padding: { top: 20, right: 20, bottom: 20, left: 20 }
}
});⚠️ Методы для работы с layout settings (
getEmailLayoutSettings,setEmailLayoutSettings) ждут инициализации письма из iframe перед возвратом или установкой данных, чтобы гарантировать актуальность информации. Методы для работы с content styles не требуют ожидания, так как стили хранятся в объекте письма.
MessageService
Типизированная надстройка над
postMessage, для обмена сообщениями между UI и sandbox.
const msg = editor.use('MessageService');
msg.send('MY_EVENT', { value: 123 });
msg.addListener('MY_EVENT', (data) => console.log(data));Выносите имена событий в общий модуль (CLI-шаблон: src/interfaces/), чтобы app и sandbox использовали одни и те же строки:
// src/interfaces/messages.ts
export const MY_PLUGIN_UPDATE = 'my-plugin:update' as const;
export type MyPluginUpdatePayload = { text: string };// app
msg.send(MY_PLUGIN_UPDATE, { text: 'hello' });
// sandbox
messageService.addListener(MY_PLUGIN_UPDATE, (data) => { /* ... */ });Тип payload задайте рядом с константой (MyPluginUpdatePayload) — отдельный runtime-пакет для идентификаторов не нужен.
SandboxScriptApi
Позволяет динамически подключать JS и CSS в iframe, например, для загрузки сторонних библиотек. Также используется для загрузки скрипта, взаимодействующего с DOM письма:
const sandboxApi = editor.use('SandboxScriptApi');
const style = sandboxApi.registerSandboxCSS('https://cdn.com/style.css');
const script = sandboxApi.registerSandboxScript('https://cdn.com/script.js');
// или по коду, если это допустимо CSP
sandboxApi.registerSandboxScriptCode('console.log("hi")');⚠️ Использование registerSandboxScriptCode может нарушить политику безопасности (CSP). Лучше предпочитать загрузку внешних скриптов через registerSandboxScript().
🧪 Плагины Sandbox
Работают внутри iframe для взаимодействия с DOM письма.
import { App } from '@emailmaker/extensions-sandbox';
import type { IPlugin } from '@emailmaker/extensions-sandbox';
class MyPluginSandbox implements IPlugin {
init() {
const messageService = App.use('MessageService');
messageService.addListener('my-plugin:update', (data) => {
const element = document.querySelector('.my-block');
if (element) {
element.textContent = data.text;
App.use('SyncService').commit();
}
});
}
dispose() {}
}
App.registerPlugin('MyPluginSandbox', MyPluginSandbox);⚠️ Sandbox работает в изолированном iframe. React-компоненты здесь недоступны — только нативный DOM.
MessageService (внутри sandbox)
Аналогичен UI-версии. Позволяет слушать и отправлять сообщения.
import { App } from '@emailmaker/extensions-sandbox';
const messageService = App.use('MessageService');
// Отправка в app
messageService.send('my-plugin:click', { elementId: '123' });
// Получение из app
messageService.addListener('my-plugin:update', (data) => {
console.log('Received:', data);
});SyncService
Для сохранения изменений из DOM в код письма:
import { App } from '@emailmaker/extensions-sandbox';
const syncService = App.use('SyncService');
document.querySelector('.title').textContent = 'Updated';
syncService.commit();⚠️ Все несохранённые (незакоммиченные) изменения могут быть утеряны при следующем рендере.
🧱 Регистрация UI компонентов
ComponentRegistry нужен, когда вы хотите зарегистрировать React-компонент и потом передавать его в API по типизированному идентификатору:
import { createComponentIdentifier } from '@emailmaker/extensions-app';
import { ComponentRegistry } from '@emailmaker/extensions-react';
const MyPanelId = createComponentIdentifier<{ groups: Group[] }>('MyPanel');
ComponentRegistry.add(MyPanelId, MyReactPanel);
ComponentRegistry.override(MyPanelId, (Base) => (props) => (
<div className="bordered"><Base {...props} /></div>
));Когда использовать
- если хотите просто показать React UI, чаще всего удобнее передать JSX напрямую
- если нужен стабильный
ComponentIdдля{ type, props }—createComponentIdentifier+ComponentRegistry - если UI без React, используйте
DomComponentRegistry
Пример с JSX:
import { SettingsPanelApi } from '@emailmaker/extensions-app';
import '@emailmaker/extensions-react';
settingsPanel.showSettingsPanel({
content: <MyPanel />,
});Пример с идентификатором:
settingsPanel.showSettingsPanel({
content: { type: MyPanelId, props: { groups } },
});🎨 UI Kit
В коде плагина импортируйте компоненты из @emailmaker/ui-kit. В release-сборке PluginDev externalizes ui-kit на singleton хоста: @emailmaker/emailmaker/ui-kit (отдельно ставить main subpath не нужно).
ColorPicker — минимальный пример
Растягивается на ширину родителя (Form.Item, колонку и т.д.) — дополнительный inline-style для ширины не нужен.
import { EmIcons, ColorPicker } from "@emailmaker/ui-kit";
<ColorPicker
value={settings.bgColor}
onChange={(v) => onChange({ ...settings, bgColor: v })}
/>FormColorField — рекомендуется для settings panel
Микролейбл + Form.Item + color picker — тот же паттерн, что в панелях редактора. Используйте внутри Form с полем name.
import { Form, FormColorField } from "@emailmaker/ui-kit";
<Form layout="vertical">
<FormColorField name="bgColor" label="Цвет фона" />
</Form>⚛️ extensions-react
@emailmaker/extensions-react нужен для React-плагинов.
Что в нем есть
ComponentRegistrycreateComponentIdentifier()- поддержка React-компонентов в API
Что можно делать
После установки пакета вы можете передавать JSX прямо в API:
import { SettingsPanelApi } from '@emailmaker/extensions-app';
import '@emailmaker/extensions-react';
settingsPanel.showSettingsPanel({
content: <MyPanel />,
});Когда использовать JSX
Прямой JSX — лучший путь по умолчанию для React-плагина:
settingsPanel.showSettingsPanel({
content: <MyPanel value={state} />,
});Он хорош, когда:
- не нужен стабильный
ComponentId - не нужен
override() - UI используется локально в одном месте
Когда использовать ComponentRegistry
ComponentRegistry нужен, если:
- компонент должен быть доступен по
ComponentId - нужна возможность
override() - компонент используется в нескольких местах через
{ type, props } - нужен стабильный идентификатор для контракта между частями плагина
Пример:
import { createComponentIdentifier } from '@emailmaker/extensions-app';
import { ComponentRegistry } from '@emailmaker/extensions-react';
const PanelId = createComponentIdentifier<{ value: string }>('MyPanel');
ComponentRegistry.add(PanelId, MyPanel);
settingsPanel.showSettingsPanel({
content: {
type: PanelId,
props: { value: 'hello' },
},
});Короткое правило
- для React-плагина ставьте
@emailmaker/extensions-react - для обычного React UI чаще всего достаточно JSX
- если нужен идентификатор компонента, используйте
ComponentRegistry
📦 Расширение типов
PluginTypeMap
PluginTypeMap — опциональный механизм для связи между плагинами по строковому имени. Позволяет использовать editor.use('PluginName') с автодополнением.
Когда нужен:
- Плагин A обращается к плагину B через
editor.use('PluginB') - URL-режимы
globals/async-globals, если плагин подключается через descriptor сresolve: 'registry'
Когда не нужен:
- Плагин подключается напрямую через класс:
plugins: [MyPlugin] - Типизация через
editor.use(MyPlugin)(прямая ссылка на класс)
declare module "@emailmaker/emailmaker" {
interface PluginTypeMap {
TestPlugin: typeof TestPlugin;
}
}PluginTypeMap наследует все базовые плагины из BasePluginTypeMap, поэтому доступны автодополнения для встроенных API (ElementsApi, MessageService, ModalApi и т.д.).
💡 Рекомендуем
editor.use(MyPlugin)с типизацией через класс напрямую.PluginTypeMapнужен дляeditor.use('Name')между плагинами и для typed descriptor-ов вида{ type: 'umd', url, name, resolve: 'registry' }.
Опции плагина
Конструктор плагина (класс или фабрика) принимает (context, options?).
Для типизации используйте ExtractPluginOptions:
import type { ExtractPluginOptions } from '@emailmaker/emailmaker';
// Опции извлекаются из конструктора
type MyOpts = ExtractPluginOptions<typeof MyPlugin>;Опции передаются через кортеж в plugins:
emailmaker.init({
plugins: [
[MyPlugin, { theme: 'dark' }]
]
});При множественной инициализации действует принцип «first-write wins» — опции фиксируются при первом создании экземпляра.
CLI-шаблон уже собирает типы отдельно (npm run build:types) и публикует dist/public-types.d.ts рядом с JS bundle, поэтому поддержку TypeScript стоит считать частью любого release target, а не только npm/ESM сценария.
Расширение Config
Публикуйте .d.ts вместе с плагином для расширения конфигурации редактора:
declare module "@emailmaker/emailmaker" {
interface Config {
productBlock?: {
enabled?: boolean;
groups: Group[];
};
}
}React-компоненты в API
Если в проекте подключен @emailmaker/extensions-react, в API можно передавать React-компоненты и JSX напрямую.
Пример:
import { SettingsPanelApi } from '@emailmaker/extensions-app';
import '@emailmaker/extensions-react';
settingsPanel.showSettingsPanel({
content: <MyPanel />,
});Если вы пишете React-плагин, просто установите
@emailmaker/extensions-react.
🧰 CLI
@emailmaker/cli — публичный генератор проектов: внешние плагины и демо-стенды. Работает в интерактивном режиме и с полным набором аргументов командной строки.
Флаги командной строки
Ниже — опции, которые отражены в справке CLI (--help). Скрытые или служебные поля (монорепозиторий, отладка шаблонов) здесь не перечисляются.
--dev / --dev-packages
Самый частый флаг для разработки: подставляет dev-версии внутренних npm-пакетов (@emailmaker-internal/*), которые в шаблонах задаются с dist-tag dev вместо latest. Используйте, когда нужны свежие предрелизные сборки экосистемы.
npx @emailmaker/cli my-plugin --kind plugin --preset advanced --bundler vite --target npm-package --dev --output-dir ./my-pluginКороткая форма: --dev (эквивалент --dev-packages).
Остальные опции
| Флаг | Назначение |
|------|------------|
| --interactive | Запустить интерактивный мастер (шаги вместо обязательных аргументов). |
| --package-name | Явное имя npm-пакета для генерируемого плагина. |
| --kind | Режим: plugin или demo-stand. |
| --stand-mode | Сценарий стенда: oauth, closed-loop, react-adapter, advanced (и связанные варианты). |
| --advanced-profile | Для --stand-mode advanced: subpath, shadow-dom, umd. |
| --preset | Шаблон плагина: minimal или advanced. |
| --bundler | vite или webpack — сборщик в шаблоне. |
| --target | Поставка плагина: npm-package, browser, advanced (advanced — только webpack; у Vite доступны npm-package и browser). |
| --advanced-target | Для --target advanced (только webpack): async-globals или legacy-async-globals. |
| --app | Идентификатор приложения (REACT_APP_NAME), как в монорепозитории; в опубликованном CLI может быть скрыт. |
| --output-dir | Каталог, куда положить сгенерированный проект. |
| --output-base | Базовый каталог: внутри него будет создана папка проекта. |
| --force | Разрешить запись в уже существующую директорию. |
Полный список и актуальные значения перечислены в выводе:
npx @emailmaker/cli --helpЧто умеет CLI
- интерактивный wizard и неинтерактивный запуск;
- генерация проекта плагина и демо-стендов;
- шаблоны под vite и webpack;
- выбор способа поставки плагина: npm-модуль, браузерный bundle (
browser), режим advanced (webpack-only).
Основные режимы
plugin
Внешний плагин: app-часть, sandbox, локальный debug host, базовые скрипты сборки.
demo-stand (в т.ч. stand, stand-closed в документации)
Отдельный стенд для интеграции: варианты с OAuth, закрытым контуром, react-adapter, advanced-профили.
Preset-ы для plugin
minimal— каркас без React-примера.advanced— React UI,ComponentRegistry, примеры панелей и API.
Примеры
С dev-пакетами:
npx @emailmaker/cli my-plugin --kind plugin --preset advanced --bundler vite --target npm-package --dev --output-dir ./my-pluginБез dev (стабильные версии из реестра):
npx @emailmaker/cli my-plugin --kind plugin --preset advanced --bundler vite --target npm-package --output-dir ./my-pluginПосле глобальной установки команда может называться emailmaker-cli.
Когда использовать CLI
- быстро создать новый внешний плагин;
- не собирать вручную конфиг vite / webpack;
- получить согласованную структуру app, sandbox и локальной отладки.
🧯 Troubleshooting
dist неожиданно толстый
Проверьте сначала:
- что release target выбран как
npm-пакетиливстраиваемый bundle, а неadvanced - что release config использует
PluginDev(...), а не ручнойexternals - что в bundle не попали
react,antdили runtime subpath-ы как обычные модули
Частая причина: release config собирается без корректного externals/alias режима.
Плагин работает в start, но ломается в release
Обычно это значит, что dev host использует PluginDev({ externals: false }), а release target уже требует runtime contract.
Проверьте локально через CLI-шаблон:
npm run build
npm run start:releaseТакже проверьте:
- target сборки в CLI / config
- release config для
src/index.ts - способ загрузки плагина в host (
npm-пакетvsвстраиваемый bundle)
Browser target не загружается
Проверьте:
- что host заранее загрузил runtime globals
- что release bundle собран под target
встраиваемый bundle - что sandbox и main bundle публикуются по ожидаемым URL
Duplicate React / hooks error
Почти всегда это значит, что React попал в bundle плагина вместо runtime externals.
Проверьте:
- используется ли
PluginDevв release config - не отключён ли externals вручную
- нет ли дополнительных alias/resolve правил, которые обходят runtime contract
Когда использовать advanced
Только если host уже требует Webpack async/globals compatibility.
Для обычного внешнего плагина:
npm-пакет— основной путьвстраиваемый bundle— для script/UMD загрузкиadvanced— только для совместимости, когда вы точно знаете контракт host
❓ FAQ / Типичные ошибки
«Invalid hook call» или два React на странице
Для обычного плагина используйте стандартные импорты
react/antdи собирайте проект черезPluginDevсalias: true(по умолчанию). Не смешивайте обычные импорты с прямыми импортами из runtime в одном plugin UI.Externals не работают в dev server
В serve-режиме externals должны быть отключены:
PluginDev({ externals: false }). Зависимости резолвятся из runtime через alias.ESM output не грузится через обычный
<script>ESM-сборка содержит
import. Используйте<script type="module">, npm-интеграцию или target browser (PluginDev({ externals: 'globals' }), UMD bundle).Browser/UMD-плагин: «Cannot read property of undefined»
Runtime хоста должен быть загружен до плагина. Подключайте через npm + lazy import, либо декларативно
{ type: 'umd', url: '…', name: 'MyPlugin', resolve: 'registry' }(сpluginRegistry.add).Не загружаются JS/CSS / iframe не отображается
Проверьте
publicPathи подключение основного плагина (VitePlugin/WebpackPlugin). В serve-режиме он обслуживает статику изnode_modules/.Панель или компонент не отображается
Убедитесь в регистрации через
ComponentRegistryиcreateComponentIdentifier.Событие не обрабатывается
Проверьте подписку на событие в
MessageServiceи совпадение строковых id в app и sandbox.Изменения в iframe теряются
Не забывайте вызывать
syncService.commit().
🔄 Миграция с предыдущих версий
С externals.json на PluginDev:
Для новых проектов используйте только PluginDev. Файл externals.json в экосистеме остаётся legacy-следом; не подключайте его вручную в Vite-шаблонах.
С pluginRegistry.add на прямой класс:
До:
pluginRegistry.add('MyPlugin', MyPlugin);
// init: plugins: ['MyPlugin']После (если не нужен editor.use('MyPlugin') между плагинами):
// init: plugins: [MyPlugin]