browser-tsx-sandbox 0.1.0
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/API.md +659 -0
- package/README.md +428 -0
- package/dist/index.cjs +270 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +107 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.js +236 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
Вот полная, подробная архитектурная документация (в формате `README.md`) для вашего проекта. Она полностью опирается на вашу Mermaid-диаграмму и детально описывает процесс динамической загрузки внешних библиотек из NPM прямо в браузере.
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# 📦 browser-tsx-sandbox (Pro Edition)
|
|
6
|
+
|
|
7
|
+
**Полностью автономная In-Browser среда (Pure Client-Side App) для компиляции TSX, рендеринга видео через Remotion и динамической загрузки NPM-зависимостей.**
|
|
8
|
+
|
|
9
|
+
Эта архитектура позволяет создавать видеоредакторы и AI-генераторы видео, работающие на 100% в браузере пользователя, без использования Node.js бэкенда для сборки бандлов.
|
|
10
|
+
|
|
11
|
+
## ✨ Ключевые возможности
|
|
12
|
+
|
|
13
|
+
- 🚀 **Zero-Backend:** Вся работа с файлами, компиляция кода и рендеринг происходят на клиенте.
|
|
14
|
+
- 📦 **Нативная поддержка NPM:** Динамический импорт любых библиотек (например, `framer-motion`, `d3`, `three.js`) напрямую с CDN (esm.sh / jsdelivr).
|
|
15
|
+
- 🎨 **Remotion + Tailwind:** Мгновенный рендеринг видеокадров и генерация служебных CSS-классов на лету (Tailwind JIT).
|
|
16
|
+
- 🧩 **Локальные ассеты:** Поддержка Drag & Drop медиафайлов через `Blob API` и `URL.createObjectURL` без загрузки на сервер.
|
|
17
|
+
- 🛡️ **Изоляция и Безопасность:** Безопасное выполнение сгенерированного ИИ кода (Shadowing глобальных переменных).
|
|
18
|
+
- 🪄 **Интеграция Lucide Icons:** Встроенный адаптер для поиска и рендеринга иконок без загрузки всей библиотеки целиком.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🚀 Быстрый старт (разработка)
|
|
23
|
+
|
|
24
|
+
Это **библиотечный пакет**, а не готовое приложение, поэтому у него нет `dev`-сервера — разработка идёт через тесты и проверку типов.
|
|
25
|
+
|
|
26
|
+
### 1. Требования
|
|
27
|
+
- Node.js 18+ (проект проверен на Node 24)
|
|
28
|
+
- npm
|
|
29
|
+
|
|
30
|
+
### 2. Установка зависимостей
|
|
31
|
+
```bash
|
|
32
|
+
npm install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 3. Команды
|
|
36
|
+
|
|
37
|
+
| Команда | Что делает |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `npm test` | один прогон всех тестов (Vitest) |
|
|
40
|
+
| `npm run test:watch` | тесты в режиме наблюдения (watch) |
|
|
41
|
+
| `npm run test:e2e` | e2e-тест реального рендера видео (Remotion + Chrome) |
|
|
42
|
+
| `npm run render` | вручную отрендерить PNG-кадр и MP4 из сцены песочницы |
|
|
43
|
+
| `npm run render:assets` | то же, но с медиа-ассетами, распакованными из ZIP |
|
|
44
|
+
| `npm run render:example` | рендер реальной анимации из `examples/remotion-scene.tsx` |
|
|
45
|
+
| `npm run render:widgets` | рендер виджетов из JSON-каталога `examples/vidora-widgets.json` |
|
|
46
|
+
| `npm run render:widgets:logo` | рендер каталога `examples/vidora-widgets-logo.json` |
|
|
47
|
+
| `npm run render:props` | рендер вариаций пропсов (проверка, что параметры меняют результат) |
|
|
48
|
+
| `npm run build` | сборка npm-пакета (`tsup` → `dist/`: ESM + CJS + типы) |
|
|
49
|
+
| `npm run pack:check` | `npm pack --dry-run` — показать содержимое тарбола |
|
|
50
|
+
| `npm run verify:video` | проверить выданный MP4 (контейнер, кодек, размеры, длительность) |
|
|
51
|
+
| `npm run typecheck` | статическая проверка типов (`tsc --noEmit`) |
|
|
52
|
+
|
|
53
|
+
### 4. Ручная проверка пайплайна
|
|
54
|
+
|
|
55
|
+
Песочницу можно прогнать на реальном файле `examples/remotion-scene.tsx`. Он компилируется из TSX в CommonJS, зависимости (`react`, `remotion`, `lucide-react`) подставляются заглушками, после чего сцена выполняется как React-компонент. Именно это проверяет интеграционный тест `src/facade.remotion.test.ts`:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx vitest run src/facade.remotion.test.ts
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 5. Реальный рендер видео (Remotion)
|
|
62
|
+
|
|
63
|
+
Папка `render/` содержит настоящий e2e-пайплайн: сцена на TSX компилируется и выполняется через `SandboxFacade`, регистрируется как Remotion-композиция и рендерится в **PNG-кадр и MP4** через headless Chrome.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm run render # -> render/out/frame-30.png и render/out/sandbox.mp4
|
|
67
|
+
npm run test:e2e # то же самое, но с проверками (размеры, наличие файлов)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Требуется установленный Google Chrome (или Edge). Путь ищется автоматически; при необходимости задайте свой:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
$env:REMOTION_BROWSER="C:\путь\к\chrome.exe"; npm run render
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> E2E-тест намеренно не входит в `npm test`: он бандлит Remotion-проект и запускает браузер, поэтому выполняется отдельно (~1 мин).
|
|
77
|
+
|
|
78
|
+
### 6. Ассеты из ZIP
|
|
79
|
+
|
|
80
|
+
Медиа-ресурсы (видео, картинки, шрифты, JSON) можно передать одним архивом. Библиотечная часть — `src/assets/zip.ts`: `extractAssetZip` распаковывает `Uint8Array` в карту байтов, `createAssetUrlMap` превращает её в `filename -> blob:url` для `SandboxFacade.setAssets`.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { extractAssetZip, createAssetUrlMap, releaseAssetUrls } from 'browser-tsx-sandbox';
|
|
84
|
+
|
|
85
|
+
const archive = extractAssetZip(zipBytes); // { 'assets/clip.mp4': Uint8Array, ... }
|
|
86
|
+
const urls = createAssetUrlMap(archive, (bytes) => URL.createObjectURL(new Blob([bytes])));
|
|
87
|
+
facade.setAssets(urls); // теперь staticFile('clip.mp4') отдаёт blob:-ссылку
|
|
88
|
+
// ...
|
|
89
|
+
releaseAssetUrls(urls, URL.revokeObjectURL);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
E2E-тест `render/assets.e2e.test.ts` кладёт реальный MP4 и SVG в ZIP, распаковывает их в `public` и рендерит сцену с `OffthreadVideo` + `Img` через Remotion `staticFile`:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm run render:assets # -> render/out/asset-still.png, render/out/asset-video.mp4
|
|
96
|
+
npm run test:e2e # оба e2e-сценария (без ассетов и с ассетами)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 7. Рендер анимации из `examples/`
|
|
100
|
+
|
|
101
|
+
`npm run render:example` берёт настоящий `examples/remotion-scene.tsx` (1177 кадров, 1920×1080) и проводит его через полный пайплайн:
|
|
102
|
+
|
|
103
|
+
1. находит в TSX абсолютные пути к видео и заменяет их на раздаваемые через `staticFile` (роль `ImportResolver`), копируя ролики в `public/b-roll/`;
|
|
104
|
+
2. компилирует Tailwind-утилиты из исходника на этапе сборки и инжектит их в сцену;
|
|
105
|
+
3. компилирует и выполняет сцену песочницей с реальными `react`, `remotion`, `lucide-react`;
|
|
106
|
+
4. рендерит по одному ключевому кадру каждого фрагмента и короткий клип.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm run render:example
|
|
110
|
+
# -> render/out/example-frame-0075.png ... example-frame-1080.png
|
|
111
|
+
# -> render/out/example-map.mp4
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
E2E-тест: `render/example.e2e.test.ts`.
|
|
115
|
+
|
|
116
|
+
### 8. Виджеты из JSON-каталога
|
|
117
|
+
|
|
118
|
+
`examples/vidora-widgets.json` — экспорт каталога виджетов (`Vidora Motion Studio`): метаданные, `default_props` и встроенный `tsx_code`. `npm run render:widgets` парсит JSON и прогоняет **каждый** `tsx_code` через песочницу:
|
|
119
|
+
|
|
120
|
+
1. Tailwind-утилиты компилируются из `tsx_code` всех виджетов;
|
|
121
|
+
2. в Remotion-проекте каждый виджет компилируется (`compileTsx`), выполняется (`executeComponent`) и регистрируется как отдельная `Composition` с `default_props` (16:9 → 1920×1080, 9:16 → 1080×1920);
|
|
122
|
+
3. рендерятся ключевые кадры каждого виджета и короткий клип.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npm run render:widgets
|
|
126
|
+
# -> render/out/widget-WordByWordText16x9-*.png
|
|
127
|
+
# -> render/out/widget-WordByWordText9x16-*.png
|
|
128
|
+
# -> render/out/widget-WordByWordText16x9.mp4
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
E2E-тест: `render/widgets.e2e.test.ts`.
|
|
132
|
+
|
|
133
|
+
Второй каталог — `examples/vidora-widgets-logo.json` (Logo Shine Badge). Пропсы влияют на рендер, а `imageUrl` разбирается и встраивается как изображение. Проверяется тестом `render/props.e2e.test.ts`:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npm run render:widgets:logo # дефолтные пропсы
|
|
137
|
+
npm run render:props # вариации: текст/размер/цвет/иконка/картинка
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> Полное описание всех контрактов и сигнатур — в [`API.md`](./API.md).
|
|
141
|
+
|
|
142
|
+
### 9. Пример использования на стороне потребителя
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { SandboxFacade } from 'browser-tsx-sandbox';
|
|
146
|
+
import * as React from 'react';
|
|
147
|
+
|
|
148
|
+
const facade = new SandboxFacade({ react: React });
|
|
149
|
+
facade.setAssets({ 'logo.png': 'blob:http://localhost/...' });
|
|
150
|
+
|
|
151
|
+
const { component: Component, error } = await facade.compile(userTsx);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 🏗 Архитектура системы
|
|
157
|
+
|
|
158
|
+
Ниже представлена полная схема взаимодействия подсистем (UI, State, Sandbox, Library Manager и Rendering Engine).
|
|
159
|
+
|
|
160
|
+
```mermaid
|
|
161
|
+
graph LR
|
|
162
|
+
classDef ui fill:#2b6cb0,stroke:#3182ce,stroke-width:2px,color:#fff;
|
|
163
|
+
classDef state fill:#38a169,stroke:#48bb78,stroke-width:2px,color:#fff;
|
|
164
|
+
classDef core fill:#805ad5,stroke:#9f7aea,stroke-width:2px,color:#fff;
|
|
165
|
+
classDef frontend fill:#dd6b20,stroke:#c05621,stroke-width:2px,color:#fff;
|
|
166
|
+
classDef api fill:#319795,stroke:#4fd1c5,stroke-width:2px,color:#fff;
|
|
167
|
+
classDef lib fill:#d69e2e,stroke:#ecc94b,stroke-width:2px,color:#fff;
|
|
168
|
+
classDef error fill:#e53e3e,stroke:#f56565,stroke-width:2px,color:#fff;
|
|
169
|
+
|
|
170
|
+
subgraph Browser ["Вкладка браузера (Pure Client-Side App)"]
|
|
171
|
+
|
|
172
|
+
subgraph UI ["UI Layer"]
|
|
173
|
+
CodeEditor["CodeEditor <br/> Monaco/CodeMirror"]:::ui
|
|
174
|
+
PromptPanel["AI Prompt Panel <br/> generate/fix component"]:::ui
|
|
175
|
+
SettingsEditor["Settings Editor <br/> fps, duration, theme"]:::ui
|
|
176
|
+
AssetPanel["Asset Manager <br/> drag and drop, list, preview"]:::ui
|
|
177
|
+
LibraryManager["Library Manager <br/> add/remove libraries"]:::ui
|
|
178
|
+
IconPicker["Lucide Icon Picker <br/> search and insert icon"]:::ui
|
|
179
|
+
StatusBar["Status Bar <br/> compiling, ready, error"]:::ui
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
subgraph State ["Application State"]
|
|
183
|
+
ProjectStore["ProjectStore <br/> files, activeFile, code"]:::state
|
|
184
|
+
SettingsStore["SettingsStore <br/> fps, duration, theme, props"]:::state
|
|
185
|
+
AssetStore["AssetStore <br/> blobUrl, mime, size, map"]:::state
|
|
186
|
+
LibraryStore["LibraryStore <br/> installed libs, versions, enabled"]:::state
|
|
187
|
+
LucideCatalog["LucideCatalog <br/> icon names, tags"]:::state
|
|
188
|
+
SandboxState["SandboxState <br/> compile status, error"]:::state
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
subgraph Sandbox ["browser-tsx-sandbox"]
|
|
192
|
+
SandboxFacade["SandboxFacade <br/> orchestrate compile and evaluate"]:::core
|
|
193
|
+
ImportResolver["ImportResolver <br/> resolve local files, assets, bare imports"]:::core
|
|
194
|
+
SyntaxChecker["SyntaxChecker <br/> parse TSX AST"]:::core
|
|
195
|
+
Compiler["SucraseCompiler <br/> TSX to CommonJS"]:::core
|
|
196
|
+
ModuleCache["ModuleCache <br/> compiled module registry"]:::core
|
|
197
|
+
DependencyContainer["DependencyContainer <br/> React, Remotion, helpers"]:::core
|
|
198
|
+
ScopeFactory["ScopeFactory <br/> DI, allowed globals, shadowing"]:::core
|
|
199
|
+
RuntimeEvaluator["RuntimeEvaluator <br/> new Function execution"]:::core
|
|
200
|
+
ErrorMapper["ErrorMapper <br/> map errors to editor position"]:::error
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
subgraph LibSystem ["Library Runtime"]
|
|
204
|
+
LibraryResolver["LibraryResolver <br/> resolve bare imports"]:::lib
|
|
205
|
+
LibraryLoader["LibraryLoader <br/> CDN, local file, blob"]:::lib
|
|
206
|
+
ModuleFormatAdapter["ModuleFormatAdapter <br/> ESM/CJS/global normalize"]:::lib
|
|
207
|
+
LibraryStyleLoader["LibraryStyleLoader <br/> css from libraries"]:::lib
|
|
208
|
+
LibraryRegistry["LibraryRegistry <br/> registered runtime modules"]:::lib
|
|
209
|
+
LucideAdapter["LucideAdapter <br/> lucide-react icon factory"]:::lib
|
|
210
|
+
LucideIconMap["LucideIconMap <br/> icon name to component"]:::lib
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
subgraph Render ["Rendering Engine"]
|
|
214
|
+
Player["Remotion Player"]:::frontend
|
|
215
|
+
CompositionRoot["CompositionRoot <br/> Composition and Root"]:::frontend
|
|
216
|
+
SceneComponent["SceneComponent <br/> user component instance"]:::frontend
|
|
217
|
+
Timeline["TimelineController <br/> play, seek, frame"]:::frontend
|
|
218
|
+
RemotionHooks["Remotion Hooks <br/> useCurrentFrame, useVideoConfig"]:::frontend
|
|
219
|
+
TailwindRuntime["Tailwind Runtime <br/> generate utility CSS"]:::frontend
|
|
220
|
+
StyleCache["StyleCache <br/> css text, invalidation"]:::frontend
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
subgraph BrowserAPIs ["Browser APIs"]
|
|
224
|
+
FileAPI["File/Blob API"]:::api
|
|
225
|
+
ObjectURL["URL.createObjectURL"]:::api
|
|
226
|
+
RAF["requestAnimationFrame"]:::api
|
|
227
|
+
Storage["localStorage/IndexedDB <br/> optional persistence"]:::api
|
|
228
|
+
Network["fetch and CDN <br/> esm.sh, jsdelivr"]:::api
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
CodeEditor -->|code change| ProjectStore
|
|
234
|
+
PromptPanel -->|generated TSX| ProjectStore
|
|
235
|
+
SettingsEditor -->|settings change| SettingsStore
|
|
236
|
+
|
|
237
|
+
AssetPanel -->|selected files| FileAPI
|
|
238
|
+
FileAPI -->|read as Blob| ObjectURL
|
|
239
|
+
ObjectURL -->|blob URL| AssetStore
|
|
240
|
+
AssetPanel -->|delete or rename| AssetStore
|
|
241
|
+
|
|
242
|
+
LibraryManager -->|add or remove| LibraryStore
|
|
243
|
+
LibraryManager -->|install request| LibraryLoader
|
|
244
|
+
IconPicker -->|search icons| LucideCatalog
|
|
245
|
+
IconPicker -->|insert import and use| CodeEditor
|
|
246
|
+
|
|
247
|
+
ProjectStore -->|raw TSX| SandboxFacade
|
|
248
|
+
SettingsStore -->|player props| CompositionRoot
|
|
249
|
+
AssetStore -->|asset map| ImportResolver
|
|
250
|
+
LibraryStore -->|enabled libraries| LibraryResolver
|
|
251
|
+
LucideCatalog --> LucideIconMap
|
|
252
|
+
|
|
253
|
+
SandboxFacade -->|step 1 resolve| ImportResolver
|
|
254
|
+
|
|
255
|
+
ImportResolver -->|bare import request| LibraryResolver
|
|
256
|
+
LibraryResolver -->|lookup| LibraryRegistry
|
|
257
|
+
LibraryResolver -->|resolved library module| ImportResolver
|
|
258
|
+
|
|
259
|
+
ImportResolver -->|resolved source| SyntaxChecker
|
|
260
|
+
SyntaxChecker -->|step 2 parse| Compiler
|
|
261
|
+
Compiler -->|CommonJS module| ModuleCache
|
|
262
|
+
ModuleCache -->|cached module| ScopeFactory
|
|
263
|
+
|
|
264
|
+
LibraryLoader -->|fetch package| Network
|
|
265
|
+
Network -->|module payload| LibraryLoader
|
|
266
|
+
LibraryLoader --> ModuleFormatAdapter
|
|
267
|
+
ModuleFormatAdapter --> LibraryRegistry
|
|
268
|
+
|
|
269
|
+
LibraryLoader -->|library css| LibraryStyleLoader
|
|
270
|
+
LibraryStyleLoader -->|inject styles| StyleCache
|
|
271
|
+
|
|
272
|
+
DependencyContainer -->|React runtime| LucideAdapter
|
|
273
|
+
LucideAdapter --> LucideIconMap
|
|
274
|
+
LucideIconMap --> LibraryRegistry
|
|
275
|
+
LibraryRegistry -->|library modules| DependencyContainer
|
|
276
|
+
|
|
277
|
+
DependencyContainer -->|allowed deps| ScopeFactory
|
|
278
|
+
ScopeFactory -->|isolated scope| RuntimeEvaluator
|
|
279
|
+
RuntimeEvaluator -->|React component| SceneComponent
|
|
280
|
+
|
|
281
|
+
SyntaxChecker -.->|syntax error| ErrorMapper
|
|
282
|
+
Compiler -.->|transform error| ErrorMapper
|
|
283
|
+
RuntimeEvaluator -.->|runtime error| ErrorMapper
|
|
284
|
+
LibraryLoader -.->|load error| ErrorMapper
|
|
285
|
+
ErrorMapper -->|friendly error| SandboxState
|
|
286
|
+
SandboxState -->|show status| StatusBar
|
|
287
|
+
|
|
288
|
+
SceneComponent -->|render tree| CompositionRoot
|
|
289
|
+
CompositionRoot -->|mounted composition| Player
|
|
290
|
+
Timeline -->|current frame| Player
|
|
291
|
+
Player -->|frame render| SceneComponent
|
|
292
|
+
RemotionHooks -->|frame and config| SceneComponent
|
|
293
|
+
|
|
294
|
+
SceneComponent -->|class names| TailwindRuntime
|
|
295
|
+
TailwindRuntime -->|generated CSS| StyleCache
|
|
296
|
+
StyleCache -->|inject styles| Player
|
|
297
|
+
|
|
298
|
+
Player -->|animation loop| RAF
|
|
299
|
+
|
|
300
|
+
ProjectStore -.->|autosave| Storage
|
|
301
|
+
SettingsStore -.->|autosave| Storage
|
|
302
|
+
AssetStore -.->|metadata save| Storage
|
|
303
|
+
LibraryStore -.->|installed libs save| Storage
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## 📦 Поддержка внешних NPM библиотек (Library Runtime)
|
|
309
|
+
|
|
310
|
+
Одной из главных фич платформы является способность "на лету" разрешать сторонние зависимости прямо в браузере, так же, как это делают CodeSandbox или StackBlitz.
|
|
311
|
+
|
|
312
|
+
### Как это работает:
|
|
313
|
+
1. **Перехват импортов (ImportResolver):**
|
|
314
|
+
Когда пользователь или ИИ пишет: `import { motion } from "framer-motion"`, `ImportResolver` понимает, что это *bare import* (запрос внешнего пакета, а не локального файла).
|
|
315
|
+
2. **Загрузка через CDN (LibraryLoader):**
|
|
316
|
+
Запрос отправляется на CDN-провайдер (например, `https://esm.sh/framer-motion`), который возвращает пакет, собранный для браузера (ESM формат).
|
|
317
|
+
3. **Адаптация модулей (ModuleFormatAdapter):**
|
|
318
|
+
Так как наш `Compiler` (Sucrase) превращает TSX в CommonJS (используя `require`), адаптер конвертирует полученный с CDN ESM-модуль так, чтобы он был доступен через `DependencyContainer`.
|
|
319
|
+
4. **Кэширование (LibraryRegistry):**
|
|
320
|
+
Скачанный модуль сохраняется в реестре. При следующем рендере сеть не используется.
|
|
321
|
+
5. **Подгрузка CSS (LibraryStyleLoader):**
|
|
322
|
+
Если библиотека поставляется с CSS (например, `import "swiper/css"`), лоадер скачивает стили и инжектит их в `StyleCache`, чтобы они сразу применились к видео.
|
|
323
|
+
|
|
324
|
+
### Особенность: Lucide Icons
|
|
325
|
+
Для работы с иконками `lucide-react` реализован отдельный микро-пайплайн. Вместо загрузки всей тяжелой библиотеки, `LucideAdapter` динамически мапит имена иконок (из `LucideCatalog`) и создает компоненты иконок "по требованию".
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## ⚙️ Детальное описание подсистем
|
|
330
|
+
|
|
331
|
+
### 1. UI Layer & State (Пользовательский интерфейс)
|
|
332
|
+
Интерфейс строится на React и менеджере состояний (Zustand/Redux).
|
|
333
|
+
- **CodeEditor:** Текстовый редактор (Monaco) для ручного написания кода.
|
|
334
|
+
- **AssetPanel:** Загрузка медиа (Drag & drop). Файлы не улетают на сервер. Браузерный `URL.createObjectURL()` мгновенно превращает локальный MP4/PNG в ссылку (`blob:http://...`), которая сохраняется в `AssetStore`.
|
|
335
|
+
- **Auto-Save:** Все состояния (`ProjectStore`, `SettingsStore`, `AssetStore`) автоматически сохраняются в `localStorage` или `IndexedDB`.
|
|
336
|
+
|
|
337
|
+
### 2. Sandbox Engine (Ядро компиляции)
|
|
338
|
+
Оркестратор, превращающий строку текста в работающий React-компонент.
|
|
339
|
+
- **SyntaxChecker:** Предварительная проверка AST дерева. Если есть ошибка, `ErrorMapper` переводит ее в понятный вид и показывает в редакторе (подчеркивает красным).
|
|
340
|
+
- **SucraseCompiler:** Самый быстрый транспилятор TSX -> JS. Вырезает типы TypeScript и превращает JSX в `React.createElement`.
|
|
341
|
+
- **ScopeFactory & RuntimeEvaluator:** Безопасное выполнение скомпилированного JS-кода через `new Function()`. `ScopeFactory` осуществляет "затенение" (Shadowing) глобальных объектов (`window`, `document`), предотвращая XSS и доступ к глобальному API браузера.
|
|
342
|
+
|
|
343
|
+
### 3. Rendering Engine (Рендеринг видео)
|
|
344
|
+
- **Remotion Player:** Сердце видеоплеера. Принимает смонтированную композицию (`CompositionRoot`) и управляет таймлайном через `requestAnimationFrame`.
|
|
345
|
+
- **Tailwind Runtime:** Сканирует отрендеренный `SceneComponent` на наличие утилитарных классов (например, `className="bg-red-500 flex"`). Генерирует CSS правила "на лету" и отправляет их в `StyleCache`, который инжектит их в DOM плеера.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## 🔄 Жизненный цикл (Data Flow)
|
|
350
|
+
|
|
351
|
+
Что происходит, когда ИИ генерирует новый TSX или пользователь нажимает клавишу в редакторе:
|
|
352
|
+
|
|
353
|
+
1. Строка кода обновляется в `ProjectStore`.
|
|
354
|
+
2. `SandboxFacade` инициирует сборку.
|
|
355
|
+
3. `ImportResolver` сканирует все `import ... from ...` в коде.
|
|
356
|
+
- Локальные ассеты заменяются на `blob:` ссылки из `AssetStore`.
|
|
357
|
+
- Неизвестные NPM библиотеки отправляются в `LibraryLoader` -> скачиваются с `esm.sh` -> попадают в `LibraryRegistry`.
|
|
358
|
+
4. Код парсится (`SyntaxChecker`) и компилируется (`Compiler`).
|
|
359
|
+
5. `DependencyContainer` собирает `require`-объекты (React, Remotion, скачанные NPM-либы).
|
|
360
|
+
6. `RuntimeEvaluator` выполняет изолированный JS-код, возвращая функцию React-компонента.
|
|
361
|
+
7. Компонент монтируется в `SceneComponent`.
|
|
362
|
+
8. `Tailwind Runtime` перехватывает классы и генерирует CSS.
|
|
363
|
+
9. `Remotion Player` отображает кадр.
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## 🛠 Установка и использование (Псевдокод внедрения)
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
npm install browser-tsx-sandbox remotion @twind/core
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**Пример использования SandboxFacade:**
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
import { SandboxFacade } from 'browser-tsx-sandbox';
|
|
377
|
+
import { useProjectStore, useAssetStore, useLibraryStore } from './store';
|
|
378
|
+
|
|
379
|
+
function PreviewPipeline() {
|
|
380
|
+
const code = useProjectStore(state => state.code);
|
|
381
|
+
const blobAssets = useAssetStore(state => state.map); // { "logo.png": "blob:..." }
|
|
382
|
+
|
|
383
|
+
const { Component, error } = useAsyncMemo(async () => {
|
|
384
|
+
const facade = new SandboxFacade();
|
|
385
|
+
|
|
386
|
+
// 1. Конфигурируем зависимости и ресурсы
|
|
387
|
+
facade.setAssets(blobAssets);
|
|
388
|
+
|
|
389
|
+
// 2. Песочница сама скачает недостающие библиотеки из импортов (NPM)
|
|
390
|
+
return await facade.compileAndEvaluate(code);
|
|
391
|
+
}, [code, blobAssets]);
|
|
392
|
+
|
|
393
|
+
if (error) return <ErrorOverlay error={error} />;
|
|
394
|
+
|
|
395
|
+
return <RemotionPlayer component={Component} {...playerSettings} />;
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## 📦 Сборка и публикация пакета
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
npm run build # tsup -> dist/ (ESM + CJS + .d.ts)
|
|
404
|
+
npm run pack:check # npm pack --dry-run: содержимое тарбола
|
|
405
|
+
npm pack # собрать browser-tsx-sandbox-<version>.tgz
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
- Форматы: `dist/index.js` (ESM), `dist/index.cjs` (CJS), `dist/index.d.ts` (`exports`-карта настроена).
|
|
409
|
+
- В публикацию попадают только `dist/`, `README.md`, `API.md` (поле `files`).
|
|
410
|
+
- `react` — `peerDependency`, `sucrase` и `fflate` — `dependencies`; все они `external` и не бандлятся.
|
|
411
|
+
- `prepack` автоматически собирает `dist/`, `prepublishOnly` прогоняет `typecheck` + `test`.
|
|
412
|
+
- Публикация: `npm publish` (при необходимости `--access public`).
|
|
413
|
+
|
|
414
|
+
Сгенерированные артефакты (`dist/`, `render/out`, `render/bundle*`, `render/.generated`, `render/public`, `*.tgz`) не коммитятся — см. `.gitignore`.
|
|
415
|
+
|
|
416
|
+
## ✅ Проверка выдачи видео
|
|
417
|
+
|
|
418
|
+
`render/verify-video.mjs` — автономный парсер MP4 (ISO BMFF): проверяет `ftyp`/`moov`, кодек `avc1`, размеры кадра и длительность. Если в системе есть `ffprobe`, дополнительно сверяет кодек, число кадров и длительность.
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
npm run verify:video render/out/sandbox.mp4 # можно и директорию с .mp4
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Пример: `majorBrand: isom`, `h264`, `640x360`, `60` кадров, `2.0s`, `valid: true`. Тест — `render/video.e2e.test.ts`.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
*Создано для систем AI-видеогенерации нового поколения. 100% Client-Side. 100% Freedom.*
|