@emailmaker/extensions-app 0.9.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1082 -0
- package/externals.json +94 -0
- package/index.js +185 -0
- package/package.json +12 -0
- package/tsconfig.json +14 -0
- package/types.d.ts +542 -0
package/README.md
ADDED
|
@@ -0,0 +1,1082 @@
|
|
|
1
|
+
# 📦 Система плагинов для [emailmaker](https://www.npmjs.com/package/@emailmaker/emailmaker)
|
|
2
|
+
|
|
3
|
+
Библиотека предоставляет расширяемую архитектуру для создания плагинов для визуального редактора писем emailmaker. Плагины позволяют:
|
|
4
|
+
|
|
5
|
+
- расширять функциональность редактора;
|
|
6
|
+
- разрабатывать собственные UI-компоненты с использованием экспортированных версий React и Ant Design
|
|
7
|
+
- взаимодействовать с кодом письма в iframe;
|
|
8
|
+
- отображать модальные окна на основе Ant Design в редакторе;
|
|
9
|
+
|
|
10
|
+
Разработка осуществляется с полной поддержкой **TypeScript**, что обеспечивает строгость типов, модульность и предсказуемость.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ⚙️ Требования
|
|
16
|
+
|
|
17
|
+
- Современный браузер с поддержкой **ES2015** (и выше)
|
|
18
|
+
- Node.js >= 14
|
|
19
|
+
- React и Ant Design подключаются через [`externals`](https://webpack.js.org/configuration/externals/) или напрямую из `@emailmaker/runtime`
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 📘 Термины
|
|
25
|
+
|
|
26
|
+
- **sandbox** — часть плагина, работающая внутри iframe, для взаимодействия с содержимым письма.
|
|
27
|
+
- **app** — часть плагина, встроенная в редактор. Отвечает за интерфейс.
|
|
28
|
+
- **pluginRegistry** — механизм регистрации плагинов.
|
|
29
|
+
- **externals** — [способ подключения внешних библиотек в webpack](https://webpack.js.org/configuration/externals/).
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🔰 Быстрый старт
|
|
35
|
+
|
|
36
|
+
👉 Самый простой способ начать — использовать [готовый шаблон](https://github.com/emailmaker/simple_plugin):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone https://github.com/emailmaker/simple_plugin
|
|
40
|
+
cd simple_plugin
|
|
41
|
+
npm install
|
|
42
|
+
npm run start
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Шаблон включает в себя:
|
|
46
|
+
- Webpack / Vite
|
|
47
|
+
- React + Ant Design
|
|
48
|
+
- TypeScript
|
|
49
|
+
- Структуру проекта, описанную ниже
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 🛠 Установка
|
|
55
|
+
|
|
56
|
+
### 📦 Основные пакеты
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install @emailmaker/emailmaker @emailmaker/runtime @emailmaker/extensions-app @emailmaker/extensions-react @emailmaker/extensions-sandbox
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Пакет | Назначение | Что экспортирует |
|
|
63
|
+
|-------|------------|------------------|
|
|
64
|
+
| `@emailmaker/emailmaker` | Основной пакет | `init()`, `prefetch()`, типы `IPlugin`, `Instance` |
|
|
65
|
+
| `@emailmaker/runtime` | Общие зависимости | React, ReactDOM, Ant Design и связанные модули |
|
|
66
|
+
| `@emailmaker/extensions-app` | API для app-плагинов | `pluginRegistry`, `DomComponentRegistry`, `ElementsApi`, `SettingsPanelApi`, `ModalApi` |
|
|
67
|
+
| `@emailmaker/extensions-react` | React API для плагинов | `ComponentRegistry`, `createComponentIdentifier()` |
|
|
68
|
+
| `@emailmaker/extensions-sandbox` | API для sandbox-плагинов | `App`, `MessageService`, `SyncService` |
|
|
69
|
+
|
|
70
|
+
`@emailmaker/ui-kit` ставится отдельно, если плагин использует готовые UI-компоненты платформы.
|
|
71
|
+
|
|
72
|
+
### Импорты
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// React и Antd — обычные импорты
|
|
76
|
+
import React from 'react';
|
|
77
|
+
import { Button } from 'antd';
|
|
78
|
+
|
|
79
|
+
// UI-компоненты платформы (опционально)
|
|
80
|
+
import { ColorPicker } from '@emailmaker/ui-kit';
|
|
81
|
+
|
|
82
|
+
// Core API плагинов
|
|
83
|
+
import { pluginRegistry, ElementsApi } from '@emailmaker/extensions-app';
|
|
84
|
+
|
|
85
|
+
// React-пакет для плагинов
|
|
86
|
+
import { createComponentIdentifier } from '@emailmaker/extensions-app';
|
|
87
|
+
import { ComponentRegistry } from '@emailmaker/extensions-react';
|
|
88
|
+
|
|
89
|
+
// Типы из основного пакета
|
|
90
|
+
import type { IPlugin, Instance } from '@emailmaker/emailmaker';
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Что обычно ставить
|
|
94
|
+
|
|
95
|
+
- если у вас обычный React-плагин, ставьте все пакеты из команды выше
|
|
96
|
+
- если используете UI Kit, добавьте еще `@emailmaker/ui-kit`
|
|
97
|
+
- если пишете только sandbox-часть, нужен `@emailmaker/extensions-sandbox`
|
|
98
|
+
|
|
99
|
+
> Для React-плагинов пакет `@emailmaker/extensions-react` нужен по умолчанию.
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 🔧 Сборка плагина
|
|
105
|
+
|
|
106
|
+
Используйте `PluginDev` для настройки бандлера. Он делает две вещи:
|
|
107
|
+
1. **alias** — настраивает React и Ant Design для работы в плагине
|
|
108
|
+
2. **externals** — выносит общие зависимости из бандла
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npm install @emailmaker/emailmaker
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Таргеты для автора плагина
|
|
115
|
+
|
|
116
|
+
| Таргет | Что получится | Внутри использует |
|
|
117
|
+
|--------|----------------|-------------------|
|
|
118
|
+
| **npm-пакет** | ESM bundle для package-based интеграции | `'esm'` |
|
|
119
|
+
| **Встраиваемый bundle** | UMD/script-подключение в host | `'globals'` |
|
|
120
|
+
| **Advanced** | Webpack-only async globals compatibility | `'async-globals'` / `'legacy-async-globals'` |
|
|
121
|
+
|
|
122
|
+
`PluginDev({ externals: false })` используется только для local dev host и не считается release target.
|
|
123
|
+
|
|
124
|
+
### Типичный проект: serve + build
|
|
125
|
+
|
|
126
|
+
Самый частый сценарий — плагин в отдельном проекте с двумя режимами:
|
|
127
|
+
|
|
128
|
+
| Режим | Entry | Externals | Основной плагин |
|
|
129
|
+
|-------|-------|-----------|-----------------|
|
|
130
|
+
| serve | `src/dev.ts` | `false` | Да (статика, iframe) |
|
|
131
|
+
| build | `src/index.ts` | зависит от выбранного target | Нет |
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// src/dev.ts — тестовая страница с основным приложением
|
|
135
|
+
import '@emailmaker/emailmaker';
|
|
136
|
+
import { MyPlugin } from './index';
|
|
137
|
+
|
|
138
|
+
emailmaker.init({ plugins: [MyPlugin] });
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Vite:**
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
import VitePlugin from '@emailmaker/emailmaker/vite';
|
|
145
|
+
import PluginDev from '@emailmaker/emailmaker/vite/pluginDev';
|
|
146
|
+
|
|
147
|
+
export default defineConfig(({ command }) => ({
|
|
148
|
+
plugins: [
|
|
149
|
+
command === 'serve' && VitePlugin(),
|
|
150
|
+
PluginDev({ externals: command === 'build' ? 'esm' : false }),
|
|
151
|
+
],
|
|
152
|
+
build: {
|
|
153
|
+
lib: { entry: 'src/index.ts', formats: ['es'] },
|
|
154
|
+
},
|
|
155
|
+
}));
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Webpack:**
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
const WebpackPlugin = require('@emailmaker/emailmaker/webpack');
|
|
162
|
+
const PluginDev = require('@emailmaker/emailmaker/webpack/pluginDev');
|
|
163
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
entry: isDev ? './src/dev.ts' : './src/index.ts',
|
|
167
|
+
plugins: [
|
|
168
|
+
isDev && new WebpackPlugin(),
|
|
169
|
+
new PluginDev({ externals: isDev ? false : 'esm' }),
|
|
170
|
+
].filter(Boolean),
|
|
171
|
+
};
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
> `VitePlugin` / `WebpackPlugin` — основной плагин для хост-приложений. В serve обслуживает iframe, шрифты и статику. В build — копирует их в output.
|
|
175
|
+
|
|
176
|
+
### Опции PluginDev
|
|
177
|
+
|
|
178
|
+
| Опция | По умолчанию | Значения |
|
|
179
|
+
|-------|-------------|----------|
|
|
180
|
+
| `externals` | `'esm'` | `'esm'`, `'globals'`, `'async-globals'` (Webpack), `false` |
|
|
181
|
+
| `alias` | `mode-aware` | `true` / `false` |
|
|
182
|
+
|
|
183
|
+
Для большинства внешних плагинов не выбирайте `externals` вручную — используйте CLI target:
|
|
184
|
+
|
|
185
|
+
- `npm-пакет` -> `esm`
|
|
186
|
+
- `встраиваемый bundle` -> `globals`
|
|
187
|
+
- `advanced` -> только если host уже требует async/globals контракт Webpack
|
|
188
|
+
|
|
189
|
+
Для `встраиваемый bundle` и `advanced` рекомендуемый runtime-контракт — descriptor вида `{ type: 'umd', url, name, resolve: 'registry' }`, где `name` совпадает с ключом из `PluginTypeMap`.
|
|
190
|
+
|
|
191
|
+
### Ручная настройка (без PluginDev)
|
|
192
|
+
|
|
193
|
+
Нужна только для продвинутой интеграции или собственного бандлерного пайплайна. Основной сценарий для внешнего плагина — отдельный пакет с `PluginDev`.
|
|
194
|
+
|
|
195
|
+
- `resolve.alias`: `react` → `@emailmaker/runtime/react` (и т.д.) для `esm` / dev; локальные shared internals идут через compat facades runtime
|
|
196
|
+
- `externals`: `@emailmaker/runtime/core`, runtime npm-модули и `@emailmaker/ui-kit`
|
|
197
|
+
- Список модулей: `@emailmaker/runtime/plugin-dev-manifest.json` → `runtimeModules`
|
|
198
|
+
|
|
199
|
+
Если release bundle неожиданно стал толстым или target `встраиваемый bundle` не загружается, начните с раздела `Troubleshooting`.
|
|
200
|
+
|
|
201
|
+
Типы для плагина всё равно собираются отдельно: CLI-шаблон запускает `build:types` и кладёт `dist/index.d.ts` рядом с JS bundle, поэтому TypeScript support сохраняется и для browser/advanced targets.
|
|
202
|
+
|
|
203
|
+
### tsconfig.json
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"compilerOptions": {
|
|
208
|
+
"jsx": "react-jsx"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Что писать в коде
|
|
214
|
+
|
|
215
|
+
В коде плагина пишите обычные импорты:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import React from 'react';
|
|
219
|
+
import { Button } from 'antd';
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
А React API для плагинов берите из `@emailmaker/extensions-react`:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { createComponentIdentifier } from '@emailmaker/extensions-app';
|
|
226
|
+
import { ComponentRegistry } from '@emailmaker/extensions-react';
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
> Обычно достаточно использовать готовый шаблон CLI и не настраивать alias/external вручную.
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 📂 PublicPath
|
|
235
|
+
|
|
236
|
+
`publicPath` используется для указания базового URL для загрузки второстепенных скриптов и статики плагина.
|
|
237
|
+
Более подробно прочитать про publicPath можно в секции [publicPath документации webpack](https://webpack.js.org/guides/public-path/)
|
|
238
|
+
|
|
239
|
+
Внутри плагина:
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
export interface MyPluginOptions {
|
|
243
|
+
publicPath?: string;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
class MyPlugin implements IPlugin {
|
|
247
|
+
constructor(
|
|
248
|
+
private editor: Instance,
|
|
249
|
+
private options: MyPluginOptions = {}
|
|
250
|
+
) {}
|
|
251
|
+
|
|
252
|
+
init() {
|
|
253
|
+
if (this.options.publicPath) {
|
|
254
|
+
const sandboxApi = this.editor.use('SandboxScriptApi');
|
|
255
|
+
sandboxApi.registerSandboxScript(this.options.publicPath + 'sandbox.js');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Подключение плагина с publicPath:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
emailmaker.init({
|
|
265
|
+
plugins: [
|
|
266
|
+
['MyPlugin', { publicPath: 'https://cdn.example.com/my-plugin/' }]
|
|
267
|
+
]
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## 📁 Структура проекта
|
|
275
|
+
|
|
276
|
+
```txt
|
|
277
|
+
plugin-root/
|
|
278
|
+
├── shared/ # Общие типы и интерфейсы
|
|
279
|
+
│ └── interfaces.ts # Идентификаторы сообщений
|
|
280
|
+
├── app/ # UI-часть, взаимодействие с редактором
|
|
281
|
+
│ └── MyPlugin.ts # Регистрация и логика плагина
|
|
282
|
+
├── sandbox/ # Работа с DOM письма
|
|
283
|
+
│ └── index.ts # Точка входа для sandbox
|
|
284
|
+
├── test/ # Точка входа для тестирования
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- `shared` — общие типы для взаимодействия между `app` и `sandbox`
|
|
288
|
+
- `app` — точка входа, из которой можно подключить скрипт sandbox:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
const sandboxApi = this.editor.use('SandboxScriptApi');
|
|
292
|
+
sandboxApi.registerSandboxScript(this.options.publicPath + 'sandbox.js');
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- `sandbox/index.ts` — точка входа в скрипт, взаимодействующий с DOM письма. Скрипт должен быть собран отдельной точкой входа, так как он исполняется в изолированной среде iframe.
|
|
296
|
+
- `test/index.tsx` — пример локального запуска редактора `emailmaker` с загрузкой плагина из `app`
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 🧩 Жизненный цикл плагина
|
|
302
|
+
|
|
303
|
+
Плагин может быть реализован в виде класса или фабрики. В конструкторе класс получает экземпляр редактора `emailmaker` и должен соответствовать интерфейсу:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
export interface IPlugin {
|
|
307
|
+
required?(): void;
|
|
308
|
+
init?(): Promise<void> | void;
|
|
309
|
+
afterInit?(): Promise<void> | void;
|
|
310
|
+
dispose?(): Promise<void> | void;
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
| Метод | Назначение |
|
|
315
|
+
|---------------|------------|
|
|
316
|
+
| `required()` | Указание зависимостей. Не обязательная, можно в init, но в сложных проектах могут быть циклические ссылки |
|
|
317
|
+
| `init()` | Инициализация плагина. Подключение других плагинов, инициализация ресурсов |
|
|
318
|
+
| `afterInit()` | Методы, вызываемые после инициализации всех плагинов |
|
|
319
|
+
| `dispose()` | Очистка ресурсов, отписка от событий |
|
|
320
|
+
|
|
321
|
+
### Пример плагина
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
import type { IPlugin, Instance } from '@emailmaker/emailmaker';
|
|
325
|
+
|
|
326
|
+
class TestPlugin implements IPlugin {
|
|
327
|
+
constructor(readonly _editor: Instance) {}
|
|
328
|
+
|
|
329
|
+
async init() {
|
|
330
|
+
try {
|
|
331
|
+
const sandboxScriptApi = this._editor.use('SandboxScriptApi');
|
|
332
|
+
// ...
|
|
333
|
+
} catch (e) {
|
|
334
|
+
console.error("Ошибка инициализации:", e);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
dispose() {
|
|
339
|
+
// Очистка ресурсов
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## 🔌 Подключение плагина
|
|
348
|
+
|
|
349
|
+
### 1. Прямой класс (рекомендуемый)
|
|
350
|
+
|
|
351
|
+
Самый простой способ — передать класс плагина напрямую:
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
import { MyPlugin } from 'my-plugin-package';
|
|
355
|
+
|
|
356
|
+
emailmaker.init({
|
|
357
|
+
plugins: [MyPlugin]
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// С опциями (second argument)
|
|
361
|
+
emailmaker.init({
|
|
362
|
+
plugins: [
|
|
363
|
+
[MyPlugin, { publicPath: 'https://cdn.example.com/my-plugin/' }]
|
|
364
|
+
]
|
|
365
|
+
});
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Конструктор плагина (класс или фабрика) принимает `(context, options?)`:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
class MyPlugin implements IPlugin {
|
|
372
|
+
constructor(private ctx: Instance, private options?: MyOptions) {}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Или фабрика
|
|
376
|
+
function myPlugin(ctx: Instance, options?: MyOptions): IPlugin {
|
|
377
|
+
return { init() { /* ... */ } };
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### 2. Ленивая загрузка
|
|
382
|
+
|
|
383
|
+
Плагин загружается асинхронно — удобно для code-splitting:
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
emailmaker.init({
|
|
387
|
+
plugins: [
|
|
388
|
+
() => import('my-plugin-package').then(m => m.MyPlugin)
|
|
389
|
+
]
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
> Результат — обычный Promise, пользователь сам выбирает нужный экспорт.
|
|
394
|
+
|
|
395
|
+
### 3. Декларативная загрузка по URL
|
|
396
|
+
|
|
397
|
+
Загрузка плагина по URL без явного импорта:
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
emailmaker.init({
|
|
401
|
+
plugins: [
|
|
402
|
+
{ type: 'esm', url: 'https://cdn.example.com/my-plugin.js' },
|
|
403
|
+
{ type: 'umd', url: 'https://cdn.example.com/my-plugin.umd.js', name: 'MyPlugin', resolve: 'registry' },
|
|
404
|
+
]
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
| Параметр | Описание |
|
|
409
|
+
|----------|----------|
|
|
410
|
+
| `type` | `'esm'` — ESM модуль (`import()`), `'umd'` — UMD/IIFE (`<script>`) |
|
|
411
|
+
| `url` | URL до скрипта плагина |
|
|
412
|
+
| `name` | Для `resolve: 'registry'` — строковый ключ из `PluginTypeMap`; для legacy `umd`/`window` — ключ в `window` |
|
|
413
|
+
| `resolve` | Опционально. `'module'` (default ESM), `'window'` (legacy default UMD), `'registry'` |
|
|
414
|
+
|
|
415
|
+
### 4. Регистрация по имени (связь между плагинами)
|
|
416
|
+
|
|
417
|
+
Если плагин A должен обращаться к плагину B через `editor.use('PluginB')` — используйте `pluginRegistry.add`:
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
// В пакете плагина
|
|
421
|
+
import { pluginRegistry } from '@emailmaker/extensions-app';
|
|
422
|
+
import { MyPlugin } from './MyPlugin';
|
|
423
|
+
|
|
424
|
+
pluginRegistry.add('MyPlugin', MyPlugin);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
// В приложении
|
|
429
|
+
emailmaker.init({
|
|
430
|
+
plugins: ['MyPlugin']
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
// В другом плагине
|
|
436
|
+
const myPlugin = this.editor.use('MyPlugin');
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
> ⚠️ Для URL-режимов `globals` / `async-globals` используйте `resolve: 'registry'` и `pluginRegistry.add(...)`. Это делает `name` проверяемым через `PluginTypeMap` и убирает зависимость от `window[name]`.
|
|
440
|
+
|
|
441
|
+
### Метод `use()`
|
|
442
|
+
|
|
443
|
+
Синхронный метод для получения экземпляра уже загруженного плагина:
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
// По классу (рекомендуемый — полная типизация)
|
|
447
|
+
const myPlugin = this.editor.use(MyPlugin);
|
|
448
|
+
|
|
449
|
+
// По имени (требует PluginTypeMap и pluginRegistry.add)
|
|
450
|
+
const myPlugin = this.editor.use('MyPlugin');
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Метод `useAsync()`
|
|
454
|
+
|
|
455
|
+
Асинхронный метод для работы с ленивыми загрузчиками и дескрипторами:
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
const myPlugin = await this.editor.useAsync(
|
|
459
|
+
() => import('my-plugin').then(m => m.MyPlugin)
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
const myPlugin = await this.editor.useAsync(
|
|
463
|
+
{ type: 'esm', url: '/plugins/analytics.js' }
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
const myPluginByKey = await this.editor.useAsync(
|
|
467
|
+
{ type: 'umd', url: '/plugins/my-plugin.umd.js', name: 'MyPlugin', resolve: 'registry' }
|
|
468
|
+
);
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## 📤 API: работа с редактором
|
|
475
|
+
|
|
476
|
+
### ElementsApi
|
|
477
|
+
|
|
478
|
+
Добавление элемента в боковую панель. Также имеется возможность изменить текущие компоненты или удалить их.
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
const elementsApi = editor.use('ElementsApi');
|
|
482
|
+
elementsApi.insert({
|
|
483
|
+
title: "Product",
|
|
484
|
+
html: "<div>...</div>",
|
|
485
|
+
name: "product-block",
|
|
486
|
+
icon: { type: 'IconSun', props: {} }
|
|
487
|
+
});
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
### SettingsPanelApi
|
|
492
|
+
|
|
493
|
+
Добавление новой панели настроек. Например, можно добавить панель которая будет отображаться при клике на элементе в sandbox.
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
const panel = editor.use('SettingsPanelApi');
|
|
497
|
+
panel.showSettingsPanel({
|
|
498
|
+
content: {
|
|
499
|
+
type: 'SettingsPanelId',
|
|
500
|
+
props: {...}
|
|
501
|
+
},
|
|
502
|
+
caption: 'Настройки',
|
|
503
|
+
deletable: true
|
|
504
|
+
});
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Примерная последовательность процесса взаимодействия sandbox и app:
|
|
508
|
+
1. Обработчик клика в sandbox
|
|
509
|
+
2. Отправка события в app через MessageService
|
|
510
|
+
3. Отображение зарегистрированной панели
|
|
511
|
+
4. Отправки изменений из панели в sandbox через MessageService
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
### ModalApi
|
|
515
|
+
|
|
516
|
+
> Интерфейс основан на модалках `Ant Design`
|
|
517
|
+
|
|
518
|
+
```ts
|
|
519
|
+
const modal = editor.use('ModalApi');
|
|
520
|
+
modal.show({
|
|
521
|
+
title: 'Выбор группы',
|
|
522
|
+
content: {
|
|
523
|
+
type: 'MyPanel',
|
|
524
|
+
props: { groups, settings }
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
### EmailSettingsApi
|
|
531
|
+
|
|
532
|
+
> API для работы с настройками письма. Позволяет получать и изменять настройки внешнего вида письма и стили элементов контента.
|
|
533
|
+
|
|
534
|
+
API разделен на два типа методов:
|
|
535
|
+
- **Layout Settings** — настройки письма целиком (фон, ширина, адаптивность)
|
|
536
|
+
- **Content Styles** — стили элементов контента (текст, заголовки, ссылки, кнопки, блоки, карточки)
|
|
537
|
+
|
|
538
|
+
#### Получение и установка настроек внешнего вида письма:
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
const emailSettingsApi = editor.use('EmailSettingsApi');
|
|
542
|
+
|
|
543
|
+
// Получить настройки внешнего вида письма
|
|
544
|
+
const layoutSettings = await emailSettingsApi.getEmailLayoutSettings();
|
|
545
|
+
console.log(layoutSettings.backgroundColor); // цвет фона письма
|
|
546
|
+
console.log(layoutSettings.width); // ширина письма {value: 600, dim: 'px'}
|
|
547
|
+
console.log(layoutSettings.responsive); // настройки адаптивности
|
|
548
|
+
|
|
549
|
+
// Изменить настройки внешнего вида письма
|
|
550
|
+
await emailSettingsApi.setEmailLayoutSettings({
|
|
551
|
+
backgroundColor: '#ffffff',
|
|
552
|
+
width: { value: 600, dim: 'px' },
|
|
553
|
+
responsive: { shutdown: false, styles: true }
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
#### Получение и установка стилей элементов контента:
|
|
558
|
+
|
|
559
|
+
```ts
|
|
560
|
+
// Получить стили элементов контента
|
|
561
|
+
const contentStyles = await emailSettingsApi.getEmailContentStyles();
|
|
562
|
+
console.log(contentStyles.text); // стили текста
|
|
563
|
+
console.log(contentStyles.buttons); // стили кнопок
|
|
564
|
+
console.log(contentStyles.block); // стили блоков
|
|
565
|
+
|
|
566
|
+
// Изменить стили элементов контента
|
|
567
|
+
await emailSettingsApi.setEmailContentStyles({
|
|
568
|
+
text: { fontSize: 18, color: '#333333' },
|
|
569
|
+
buttons: {
|
|
570
|
+
backgroundColor: '#1890ff',
|
|
571
|
+
borderRadius: { all: 8 }
|
|
572
|
+
},
|
|
573
|
+
block: {
|
|
574
|
+
padding: { top: 20, right: 20, bottom: 20, left: 20 }
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
> ⚠️ Методы для работы с layout settings (`getEmailLayoutSettings`, `setEmailLayoutSettings`) ждут инициализации письма из iframe перед возвратом или установкой данных, чтобы гарантировать актуальность информации. Методы для работы с content styles не требуют ожидания, так как стили хранятся в объекте письма.
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
### MessageService
|
|
583
|
+
|
|
584
|
+
> Типизированная надстройка над `postMessage`, для обмена сообщениями между UI и sandbox.
|
|
585
|
+
|
|
586
|
+
```ts
|
|
587
|
+
const msg = editor.use('MessageService');
|
|
588
|
+
msg.send('MY_EVENT', { value: 123 });
|
|
589
|
+
|
|
590
|
+
msg.addListener('MY_EVENT', (data) => console.log(data));
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
Рекомендуется выносить события в константы и использовать типизацию:
|
|
594
|
+
|
|
595
|
+
```ts
|
|
596
|
+
import { createIdentifier } from 'di';
|
|
597
|
+
|
|
598
|
+
export const Activate_Product_Settings = createIdentifier<
|
|
599
|
+
{ groupId?: string; visualSettings: VisualSettings },
|
|
600
|
+
void,
|
|
601
|
+
void
|
|
602
|
+
>('Activate_Product_Settings');
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
### SandboxScriptApi
|
|
607
|
+
|
|
608
|
+
Позволяет динамически подключать JS и CSS в iframe, например, для загрузки сторонних библиотек. Также используется для загрузки скрипта, взаимодействующего с DOM письма:
|
|
609
|
+
|
|
610
|
+
```ts
|
|
611
|
+
const sandboxApi = editor.use('SandboxScriptApi');
|
|
612
|
+
|
|
613
|
+
const style = sandboxApi.registerSandboxCSS('https://cdn.com/style.css');
|
|
614
|
+
const script = sandboxApi.registerSandboxScript('https://cdn.com/script.js');
|
|
615
|
+
|
|
616
|
+
// или по коду, если это допустимо CSP
|
|
617
|
+
sandboxApi.registerSandboxScriptCode('console.log("hi")');
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
⚠️ Использование `registerSandboxScriptCode` может нарушить политику безопасности (CSP). Лучше предпочитать загрузку внешних скриптов через `registerSandboxScript()`.
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## 🧪 Плагины Sandbox
|
|
626
|
+
|
|
627
|
+
Работают внутри iframe для взаимодействия с DOM письма.
|
|
628
|
+
|
|
629
|
+
```ts
|
|
630
|
+
import { App } from '@emailmaker/extensions-sandbox';
|
|
631
|
+
import type { IPlugin } from '@emailmaker/extensions-sandbox';
|
|
632
|
+
|
|
633
|
+
class MyPluginSandbox implements IPlugin {
|
|
634
|
+
init() {
|
|
635
|
+
const messageService = App.use('MessageService');
|
|
636
|
+
|
|
637
|
+
messageService.addListener('my-plugin:update', (data) => {
|
|
638
|
+
const element = document.querySelector('.my-block');
|
|
639
|
+
if (element) {
|
|
640
|
+
element.textContent = data.text;
|
|
641
|
+
App.use('SyncService').commit();
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
dispose() {}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
App.registerPlugin('MyPluginSandbox', MyPluginSandbox);
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
> ⚠️ Sandbox работает в изолированном iframe. React-компоненты здесь недоступны — только нативный DOM.
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
### MessageService (внутри sandbox)
|
|
656
|
+
|
|
657
|
+
Аналогичен UI-версии. Позволяет слушать и отправлять сообщения.
|
|
658
|
+
|
|
659
|
+
```ts
|
|
660
|
+
import { App } from '@emailmaker/extensions-sandbox';
|
|
661
|
+
|
|
662
|
+
const messageService = App.use('MessageService');
|
|
663
|
+
|
|
664
|
+
// Отправка в app
|
|
665
|
+
messageService.send('my-plugin:click', { elementId: '123' });
|
|
666
|
+
|
|
667
|
+
// Получение из app
|
|
668
|
+
messageService.addListener('my-plugin:update', (data) => {
|
|
669
|
+
console.log('Received:', data);
|
|
670
|
+
});
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
### SyncService
|
|
675
|
+
|
|
676
|
+
Для сохранения изменений из DOM в код письма:
|
|
677
|
+
|
|
678
|
+
```ts
|
|
679
|
+
import { App } from '@emailmaker/extensions-sandbox';
|
|
680
|
+
|
|
681
|
+
const syncService = App.use('SyncService');
|
|
682
|
+
|
|
683
|
+
document.querySelector('.title').textContent = 'Updated';
|
|
684
|
+
syncService.commit();
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
> ⚠️ Все несохранённые (незакоммиченные) изменения могут быть утеряны при следующем рендере.
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
---
|
|
691
|
+
|
|
692
|
+
## 🧱 Регистрация UI компонентов
|
|
693
|
+
|
|
694
|
+
`ComponentRegistry` нужен, когда вы хотите зарегистрировать React-компонент и потом передавать его в API по идентификатору:
|
|
695
|
+
|
|
696
|
+
```ts
|
|
697
|
+
import { ComponentRegistry } from '@emailmaker/extensions-react';
|
|
698
|
+
|
|
699
|
+
ComponentRegistry.add('MyPanel', MyReactPanel);
|
|
700
|
+
|
|
701
|
+
ComponentRegistry.override('MyPanel', (Base) => (props) => (
|
|
702
|
+
<div className="bordered"><Base {...props} /></div>
|
|
703
|
+
));
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
## Когда использовать
|
|
707
|
+
|
|
708
|
+
- если хотите просто показать React UI, чаще всего удобнее передать JSX напрямую
|
|
709
|
+
- если нужен `ComponentId`, используйте `ComponentRegistry`
|
|
710
|
+
- если UI без React, используйте `DomComponentRegistry`
|
|
711
|
+
|
|
712
|
+
Пример с JSX:
|
|
713
|
+
|
|
714
|
+
```ts
|
|
715
|
+
import { SettingsPanelApi } from '@emailmaker/extensions-app';
|
|
716
|
+
import '@emailmaker/extensions-react';
|
|
717
|
+
|
|
718
|
+
settingsPanel.showSettingsPanel({
|
|
719
|
+
content: <MyPanel />,
|
|
720
|
+
});
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
---
|
|
725
|
+
|
|
726
|
+
## 🎨 UI Kit
|
|
727
|
+
|
|
728
|
+
```tsx
|
|
729
|
+
import { EmIcons, ColorPicker } from "@emailmaker/ui-kit";
|
|
730
|
+
|
|
731
|
+
<ColorPicker
|
|
732
|
+
value={settings.bgColor}
|
|
733
|
+
onChange={(v) => onChange({ ...settings, bgColor: v })}
|
|
734
|
+
/>
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
|
|
740
|
+
# ⚛️ `extensions-react`
|
|
741
|
+
|
|
742
|
+
`@emailmaker/extensions-react` нужен для React-плагинов.
|
|
743
|
+
|
|
744
|
+
## Что в нем есть
|
|
745
|
+
|
|
746
|
+
- `ComponentRegistry`
|
|
747
|
+
- `createComponentIdentifier()`
|
|
748
|
+
- поддержка React-компонентов в API
|
|
749
|
+
|
|
750
|
+
## Что можно делать
|
|
751
|
+
|
|
752
|
+
После установки пакета вы можете передавать JSX прямо в API:
|
|
753
|
+
|
|
754
|
+
```ts
|
|
755
|
+
import { SettingsPanelApi } from '@emailmaker/extensions-app';
|
|
756
|
+
import '@emailmaker/extensions-react';
|
|
757
|
+
|
|
758
|
+
settingsPanel.showSettingsPanel({
|
|
759
|
+
content: <MyPanel />,
|
|
760
|
+
});
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
## Когда использовать JSX
|
|
764
|
+
|
|
765
|
+
Прямой JSX — лучший путь по умолчанию для React-плагина:
|
|
766
|
+
|
|
767
|
+
```ts
|
|
768
|
+
settingsPanel.showSettingsPanel({
|
|
769
|
+
content: <MyPanel value={state} />,
|
|
770
|
+
});
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
Он хорош, когда:
|
|
774
|
+
|
|
775
|
+
- не нужен стабильный `ComponentId`
|
|
776
|
+
- не нужен `override()`
|
|
777
|
+
- UI используется локально в одном месте
|
|
778
|
+
|
|
779
|
+
## Когда использовать `ComponentRegistry`
|
|
780
|
+
|
|
781
|
+
`ComponentRegistry` нужен, если:
|
|
782
|
+
|
|
783
|
+
- компонент должен быть доступен по `ComponentId`
|
|
784
|
+
- нужна возможность `override()`
|
|
785
|
+
- компонент используется в нескольких местах через `{ type, props }`
|
|
786
|
+
- нужен стабильный идентификатор для контракта между частями плагина
|
|
787
|
+
|
|
788
|
+
Пример:
|
|
789
|
+
|
|
790
|
+
```ts
|
|
791
|
+
import { createComponentIdentifier } from '@emailmaker/extensions-app';
|
|
792
|
+
import { ComponentRegistry } from '@emailmaker/extensions-react';
|
|
793
|
+
|
|
794
|
+
const PanelId = createComponentIdentifier<{ value: string }>('MyPanel');
|
|
795
|
+
|
|
796
|
+
ComponentRegistry.add(PanelId, MyPanel);
|
|
797
|
+
|
|
798
|
+
settingsPanel.showSettingsPanel({
|
|
799
|
+
content: {
|
|
800
|
+
type: PanelId,
|
|
801
|
+
props: { value: 'hello' },
|
|
802
|
+
},
|
|
803
|
+
});
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
## Короткое правило
|
|
807
|
+
|
|
808
|
+
- для React-плагина ставьте `@emailmaker/extensions-react`
|
|
809
|
+
- для обычного React UI чаще всего достаточно JSX
|
|
810
|
+
- если нужен идентификатор компонента, используйте `ComponentRegistry`
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
---
|
|
814
|
+
|
|
815
|
+
## 📦 Расширение типов
|
|
816
|
+
|
|
817
|
+
### PluginTypeMap
|
|
818
|
+
|
|
819
|
+
`PluginTypeMap` — **опциональный** механизм для связи между плагинами по строковому имени. Позволяет использовать `editor.use('PluginName')` с автодополнением.
|
|
820
|
+
|
|
821
|
+
**Когда нужен:**
|
|
822
|
+
- Плагин A обращается к плагину B через `editor.use('PluginB')`
|
|
823
|
+
- URL-режимы `globals` / `async-globals`, если плагин подключается через descriptor с `resolve: 'registry'`
|
|
824
|
+
|
|
825
|
+
**Когда не нужен:**
|
|
826
|
+
- Плагин подключается напрямую через класс: `plugins: [MyPlugin]`
|
|
827
|
+
- Типизация через `editor.use(MyPlugin)` (прямая ссылка на класс)
|
|
828
|
+
|
|
829
|
+
```ts
|
|
830
|
+
declare module "@emailmaker/emailmaker" {
|
|
831
|
+
interface PluginTypeMap {
|
|
832
|
+
TestPlugin: typeof TestPlugin;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
`PluginTypeMap` наследует все базовые плагины из `BasePluginTypeMap`, поэтому доступны автодополнения для встроенных API (ElementsApi, MessageService, ModalApi и т.д.).
|
|
838
|
+
|
|
839
|
+
> 💡 Рекомендуем `editor.use(MyPlugin)` с типизацией через класс напрямую. `PluginTypeMap` нужен для `editor.use('Name')` между плагинами и для typed descriptor-ов вида `{ type: 'umd', url, name, resolve: 'registry' }`.
|
|
840
|
+
|
|
841
|
+
### Опции плагина
|
|
842
|
+
|
|
843
|
+
Конструктор плагина (класс или фабрика) принимает `(context, options?)`.
|
|
844
|
+
Для типизации используйте `ExtractPluginOptions`:
|
|
845
|
+
|
|
846
|
+
```ts
|
|
847
|
+
import type { ExtractPluginOptions } from '@emailmaker/emailmaker';
|
|
848
|
+
|
|
849
|
+
// Опции извлекаются из конструктора
|
|
850
|
+
type MyOpts = ExtractPluginOptions<typeof MyPlugin>;
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
Опции передаются через кортеж в `plugins`:
|
|
854
|
+
|
|
855
|
+
```ts
|
|
856
|
+
emailmaker.init({
|
|
857
|
+
plugins: [
|
|
858
|
+
[MyPlugin, { theme: 'dark' }]
|
|
859
|
+
]
|
|
860
|
+
});
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
> При множественной инициализации действует принцип «first-write wins» — опции фиксируются при первом создании экземпляра.
|
|
864
|
+
|
|
865
|
+
CLI-шаблон уже собирает типы отдельно (`npm run build:types`) и публикует `dist/index.d.ts`, поэтому поддержку TypeScript стоит считать частью любого release target, а не только npm/ESM сценария.
|
|
866
|
+
|
|
867
|
+
### Расширение Config
|
|
868
|
+
|
|
869
|
+
Публикуйте `.d.ts` вместе с плагином для расширения конфигурации редактора:
|
|
870
|
+
|
|
871
|
+
```ts
|
|
872
|
+
declare module "@emailmaker/emailmaker" {
|
|
873
|
+
interface Config {
|
|
874
|
+
productBlock?: {
|
|
875
|
+
enabled?: boolean;
|
|
876
|
+
groups: Group[];
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
### React-компоненты в API
|
|
883
|
+
|
|
884
|
+
Если в проекте подключен `@emailmaker/extensions-react`, в API можно передавать React-компоненты и JSX напрямую.
|
|
885
|
+
|
|
886
|
+
Пример:
|
|
887
|
+
|
|
888
|
+
```ts
|
|
889
|
+
import { SettingsPanelApi } from '@emailmaker/extensions-app';
|
|
890
|
+
import '@emailmaker/extensions-react';
|
|
891
|
+
|
|
892
|
+
settingsPanel.showSettingsPanel({
|
|
893
|
+
content: <MyPanel />,
|
|
894
|
+
});
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
> Если вы пишете React-плагин, просто установите `@emailmaker/extensions-react`.
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
---
|
|
901
|
+
|
|
902
|
+
# 🧰 CLI
|
|
903
|
+
|
|
904
|
+
`@emailmaker/cli` — публичный генератор проектов для внешних плагинов и demo-стендов.
|
|
905
|
+
|
|
906
|
+
## Что умеет CLI
|
|
907
|
+
|
|
908
|
+
- интерактивный wizard
|
|
909
|
+
- неинтерактивный запуск через аргументы командной строки
|
|
910
|
+
- генерация plugin-проекта
|
|
911
|
+
- генерация stand / stand-closed сценариев
|
|
912
|
+
- шаблоны под `vite` и `webpack`
|
|
913
|
+
- выбор target-а для внешнего плагина: `npm-пакет`, `встраиваемый bundle`, `advanced`
|
|
914
|
+
|
|
915
|
+
## Основные режимы
|
|
916
|
+
|
|
917
|
+
### `plugin`
|
|
918
|
+
|
|
919
|
+
Генерирует внешний плагин с:
|
|
920
|
+
|
|
921
|
+
- app-частью
|
|
922
|
+
- sandbox-частью
|
|
923
|
+
- local debug host
|
|
924
|
+
- базовой сборкой и scripts
|
|
925
|
+
|
|
926
|
+
### `stand`
|
|
927
|
+
|
|
928
|
+
Генерирует тестовый стенд с API и OAuth.
|
|
929
|
+
|
|
930
|
+
### `stand-closed`
|
|
931
|
+
|
|
932
|
+
Генерирует стенд для замкнутого контура (`standaloneKey`).
|
|
933
|
+
|
|
934
|
+
## Preset-ы для plugin
|
|
935
|
+
|
|
936
|
+
### `minimal`
|
|
937
|
+
|
|
938
|
+
Минимальный каркас:
|
|
939
|
+
|
|
940
|
+
- `pluginRegistry`
|
|
941
|
+
- базовая app/sandbox связка
|
|
942
|
+
- без React UI примера
|
|
943
|
+
|
|
944
|
+
### `advanced`
|
|
945
|
+
|
|
946
|
+
Расширенный пример:
|
|
947
|
+
|
|
948
|
+
- React UI
|
|
949
|
+
- `ComponentRegistry` из `@emailmaker/extensions-react`
|
|
950
|
+
- пример регистрации panel/icon
|
|
951
|
+
- пример `SettingsPanelApi` и `MessageService`
|
|
952
|
+
|
|
953
|
+
## Пример запуска
|
|
954
|
+
|
|
955
|
+
```bash
|
|
956
|
+
npx @emailmaker/cli my-plugin --preset advanced --bundler vite --target npm-package --output-dir ./my-plugin
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
## Когда использовать CLI
|
|
960
|
+
|
|
961
|
+
CLI нужен, если вы хотите:
|
|
962
|
+
|
|
963
|
+
- быстро стартовать новый внешний плагин
|
|
964
|
+
- не собирать вручную `vite`/`webpack` конфиг
|
|
965
|
+
- сразу получить готовую структуру проекта
|
|
966
|
+
- получить рабочий пример app, sandbox и локальной отладки
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
---
|
|
970
|
+
|
|
971
|
+
## 🧯 Troubleshooting
|
|
972
|
+
|
|
973
|
+
### `dist` неожиданно толстый
|
|
974
|
+
|
|
975
|
+
Проверьте сначала:
|
|
976
|
+
|
|
977
|
+
- что release target выбран как `npm-пакет` или `встраиваемый bundle`, а не `advanced`
|
|
978
|
+
- что release config использует `PluginDev(...)`, а не ручной `externals`
|
|
979
|
+
- что в bundle не попали `react`, `antd` или runtime subpath-ы как обычные модули
|
|
980
|
+
|
|
981
|
+
Частая причина: release config собирается без корректного externals/alias режима.
|
|
982
|
+
|
|
983
|
+
### Плагин работает в `start`, но ломается в release
|
|
984
|
+
|
|
985
|
+
Обычно это значит, что dev host использует `PluginDev({ externals: false })`, а release target уже требует runtime contract.
|
|
986
|
+
|
|
987
|
+
Проверьте:
|
|
988
|
+
|
|
989
|
+
- target сборки в CLI / config
|
|
990
|
+
- release config для `src/index.ts`
|
|
991
|
+
- способ загрузки плагина в host (`npm-пакет` vs `встраиваемый bundle`)
|
|
992
|
+
|
|
993
|
+
### Browser target не загружается
|
|
994
|
+
|
|
995
|
+
Проверьте:
|
|
996
|
+
|
|
997
|
+
- что host заранее загрузил runtime globals
|
|
998
|
+
- что release bundle собран под target `встраиваемый bundle`
|
|
999
|
+
- что sandbox и main bundle публикуются по ожидаемым URL
|
|
1000
|
+
|
|
1001
|
+
### Duplicate React / hooks error
|
|
1002
|
+
|
|
1003
|
+
Почти всегда это значит, что React попал в bundle плагина вместо runtime externals.
|
|
1004
|
+
|
|
1005
|
+
Проверьте:
|
|
1006
|
+
|
|
1007
|
+
- используется ли `PluginDev` в release config
|
|
1008
|
+
- не отключён ли externals вручную
|
|
1009
|
+
- нет ли дополнительных alias/resolve правил, которые обходят runtime contract
|
|
1010
|
+
|
|
1011
|
+
### Когда использовать `advanced`
|
|
1012
|
+
|
|
1013
|
+
Только если host уже требует Webpack async/globals compatibility.
|
|
1014
|
+
|
|
1015
|
+
Для обычного внешнего плагина:
|
|
1016
|
+
|
|
1017
|
+
- `npm-пакет` — основной путь
|
|
1018
|
+
- `встраиваемый bundle` — для script/UMD загрузки
|
|
1019
|
+
- `advanced` — только для совместимости, когда вы точно знаете контракт host
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
## ❓ FAQ / Типичные ошибки
|
|
1025
|
+
|
|
1026
|
+
- **«Invalid hook call» или два React на странице**
|
|
1027
|
+
> Для обычного плагина используйте стандартные импорты `react` / `antd` и собирайте проект через `PluginDev` с `alias: true` (по умолчанию). Не смешивайте обычные импорты с прямыми импортами из runtime в одном plugin UI.
|
|
1028
|
+
|
|
1029
|
+
- **Externals не работают в dev server**
|
|
1030
|
+
> В serve-режиме externals должны быть отключены: `PluginDev({ externals: false })`. Зависимости резолвятся из runtime через alias.
|
|
1031
|
+
|
|
1032
|
+
- **ESM output не грузится через `<script>`**
|
|
1033
|
+
> ESM-сборка содержит `import` и не работает как обычный скрипт. Используйте `<script type="module">` или соберите плагин в режиме `global` / `iife`.
|
|
1034
|
+
|
|
1035
|
+
- **Global-плагин: «Cannot read property of undefined»**
|
|
1036
|
+
> Runtime должен быть загружен до плагина. Подключайте плагин через ленивый импорт `() => import(…)` или декларативно `{ type: 'global', url: '…' }`.
|
|
1037
|
+
|
|
1038
|
+
- **Не загружаются JS/CSS / iframe не отображается**
|
|
1039
|
+
> Проверьте `publicPath` и подключение основного плагина (`VitePlugin` / `WebpackPlugin`). В serve-режиме он обслуживает статику из `node_modules/`.
|
|
1040
|
+
|
|
1041
|
+
- **Панель или компонент не отображается**
|
|
1042
|
+
> Убедитесь в регистрации через `ComponentRegistry`.
|
|
1043
|
+
|
|
1044
|
+
- **Событие не обрабатывается**
|
|
1045
|
+
> Проверьте подписку на событие в `MessageService`.
|
|
1046
|
+
|
|
1047
|
+
- **Изменения в iframe теряются**
|
|
1048
|
+
> Не забывайте вызывать `syncService.commit()`.
|
|
1049
|
+
|
|
1050
|
+
---
|
|
1051
|
+
|
|
1052
|
+
### 🔄 Миграция с предыдущих версий
|
|
1053
|
+
|
|
1054
|
+
**С `externals.json` на `PluginDev`:**
|
|
1055
|
+
|
|
1056
|
+
Старый подход с `externals.json` продолжает работать — файл генерируется для обратной совместимости. Но рекомендуем перейти на `PluginDev`: удалите загрузку `externals.json` из конфига и подключите плагин — он настроит externals и alias автоматически.
|
|
1057
|
+
|
|
1058
|
+
**С `pluginRegistry.add` на прямой класс:**
|
|
1059
|
+
|
|
1060
|
+
До:
|
|
1061
|
+
```typescript
|
|
1062
|
+
pluginRegistry.add('MyPlugin', MyPlugin);
|
|
1063
|
+
// init: plugins: ['MyPlugin']
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
После (если не нужен `editor.use('MyPlugin')` между плагинами):
|
|
1067
|
+
```typescript
|
|
1068
|
+
// init: plugins: [MyPlugin]
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
---
|
|
1073
|
+
|
|
1074
|
+
## 🔗 Полезные ссылки
|
|
1075
|
+
|
|
1076
|
+
- [Webpack](https://github.com/webpack/webpack)
|
|
1077
|
+
- [Vite](https://vitejs.dev/)
|
|
1078
|
+
- [React](https://github.com/facebook/react)
|
|
1079
|
+
- [TypeScript](https://github.com/microsoft/TypeScript)
|
|
1080
|
+
- [Ant Design](https://github.com/ant-design/ant-design)
|
|
1081
|
+
- [Пример плагина](https://github.com/emailmaker/simple_plugin)
|
|
1082
|
+
|