OpenPencil vs Penpot: сравнение архитектуры и производительности
Зачем сравнивать? OpenPencil существует, потому что закрытые дизайн-платформы ограничивают возможности. Понимание архитектурных различий показывает, что может предложить открытая, локальная альтернатива.
WASM-рендерер Penpot
Penpot 2.x включает Rust/Skia WASM рендерер (render-wasm/v1), который можно включить через серверные флаги или параметр ?wasm=true в URL. Старый SVG-рендерер остаётся по умолчанию. На этой странице рассматриваются оба варианта.
1. Масштаб и размер кодовой базы
| Метрика | OpenPencil | Penpot |
|---|---|---|
| Всего строк кода | ~26 000 | ~299 000 |
| Файлов исходного кода | ~143 | ~2 900 |
| Языки | TypeScript, Vue | Clojure, ClojureScript, Rust, JS, SQL, SCSS |
| Движок рендеринга | ~3 200 строк (TS, 10 файлов) | 22 000 строк (Rust/Skia WASM) |
| UI-код | ~4 500 строк | ~175 000 строк (CLJS + SCSS) |
| Бэкенд | Нет (локальный подход) | 32 600 строк + 151 SQL-файл |
| Соотношение LOC | 1× | ~11× |
OpenPencil примерно в 11 раз меньше — и в этом вся суть. Это не упрощение, а принципиально другая архитектура.
2. Архитектура
OpenPencil: монолитный клиент
┌─────────────────────────────────┐
│ Tauri (native shell) │
│ ┌───────────────────────────┐ │
│ │ Vue 3 + TypeScript │ │
│ │ ┌─────────┐ ┌──────────┐│ │
│ │ │ Editor │ │ Kiwi ││ │
│ │ │ Store │ │ Codec ││ │
│ │ └────┬─────┘ └──────────┘│ │
│ │ │ │ │
│ │ ┌────▼────────────────┐ │ │
│ │ │ Scene Graph (TS) │ │ │
│ │ │ Map<string, Node> │ │ │
│ │ └────┬────────────────┘ │ │
│ │ │ │ │
│ │ ┌────▼────┐ ┌──────────┐│ │
│ │ │ Skia │ │ Yoga ││ │
│ │ │CanvasKit│ │ Layout ││ │
│ │ │ (WASM) │ │ (WASM) ││ │
│ │ └─────────┘ └──────────┘│ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘Всё в одном процессе. Без сервера, без базы данных, без Docker. Граф сцены — плоская Map<string, SceneNode> в TypeScript. Рендеринг вызывает Skia CanvasKit напрямую из TS. Макет — Yoga WASM, вызываемый синхронно.
Penpot: распределённый клиент-сервер
┌───────────────────────────────────────────────────────┐
│ Docker Compose │
│ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ Frontend │ │ Backend │ │ Exporter │ │
│ │ ClojureScript│ │ Clojure │ │ (Chromium) │ │
│ │ shadow-cljs │ │ JVM │ │ │ │
│ │ ┌─────────┐ │ │ ┌────────┐ │ └──────────────┘ │
│ │ │render- │ │ │ │Postgres│ │ │
│ │ │wasm │ │ │ │Valkey │ │ ┌──────────────┐ │
│ │ │(Rust→ │ │ │ │ MinIO │ │ │ MCP │ │
│ │ │ Skia │ │ │ └────────┘ │ │ Server │ │
│ │ │ WASM) │ │ │ │ └──────────────┘ │
│ │ └─────────┘ │ │ │ │
│ └──────────────┘ └─────────────┘ │
└───────────────────────────────────────────────────────┘Минимум 5+ сервисов. PostgreSQL для хранения, Redis (Valkey) для pub/sub и кеширования, MinIO для хранения ассетов, JVM-бэкенд, Node.js экспортёр (headless Chromium для серверного рендеринга), плюс ClojureScript фронтенд. Для разработки требуется Docker Compose с настроенной сетью.
Вердикт: архитектура
Однопроцессная архитектура OpenPencil устраняет:
- Сетевую задержку между фронтендом и бэкендом
- Накладные расходы на сериализацию/десериализацию на границах сервисов
- Сложность оркестрации контейнеров
- Накладные расходы на запросы к базе данных при каждой операции
Архитектура Penpot оптимизирована для многопользовательских серверных развёртываний. OpenPencil оптимизирован для мгновенной локальной производительности.
3. Конвейер рендеринга
OpenPencil: TS → CanvasKit WASM (напрямую)
// renderer.ts — прямые вызовы CanvasKit из TypeScript
renderSceneToCanvas(canvas, graph, pageId) {
// Итерируем узлы, строим Skia paths/paints, рисуем
this.fillPaint.setColor(...)
canvas.drawRRect(rrect, this.fillPaint)
}- 1 пересечение границы: TS → WASM (CanvasKit)
- Граф сцены живёт в JS-куче — без сериализации для рендеринга
- ~3 200 строк рендерера (разбит на 10 специализированных файлов: scene, overlays, fills, strokes, shapes, effects, rulers, labels)
Penpot: JS (скомпилированный из CLJS) → Rust WASM → Skia
Penpot 2.x включает Rust/Skia WASM рендерер (render-wasm/v1), опционально включаемый через серверные флаги или ?wasm=true. При включении фигуры рендерятся через:
ClojureScript (скомпилированный в JS)
→ декомпозиция в примитивы + бинарная упаковка в линейную память WASM
→ Rust WASM (через Emscripten C FFI)
→ skia-safe (Rust-биндинги Skia)
→ Skia (WebGL)При отключении (по умолчанию) фигуры рендерятся как SVG DOM-дерево через React/Reagent — каждая фигура является DOM-элементом.
- 1 пересечение границы (JS → WASM), как и у OpenPencil — но с явными накладными расходами на сериализацию: UUID разбиваются на 4×u32, трансформации на 6×f32, заливки/обводки упаковываются в бинарный формат, базовые свойства группируются в 104-байтную структуру на фигуру
- Тайловая система рендеринга с областями интереса
- 11 отдельных поверхностей рендеринга (заливки, обводки, тени и т.д.)
- Глобальное мутабельное состояние через паттерн
unsafe { STATE.as_mut() } - 22 000 строк Rust-движка рендеринга
Тайловая система Penpot (TileViewbox, TileTextureCache, TILE_SIZE_MULTIPLIER) предварительно рендерит тайлы вокруг области видимости и кеширует текстуры (до 1024 записей).
OpenPencil перерисовывает всю область видимости каждый кадр, потому что CanvasKit, вызываемый напрямую из TS, достаточно быстр и не нуждается в тайлинге.
Вердикт: рендеринг
| Аспект | OpenPencil | Penpot |
|---|---|---|
| Граница JS→WASM | Напрямую (TS-объекты) | Бинарная упаковка (104-байтная структура) |
| Модель рендеринга | Немедленная/полная перерисовка | Тайловое кеширование |
| Управление поверхностями | 1 поверхность | 11 поверхностей |
| Расход памяти | Низкий (без кеша тайлов) | Высокий (кеш на 1024 тайла) |
| Сложность кода | ~3 200 строк (10 файлов) | 22 000 строк |
| Unsafe-код | Нет | unsafe глобальное состояние |
Когда WASM-рендерер Penpot включён, оба проекта используют Skia через JS→WASM. OpenPencil вызывает CanvasKit напрямую с TS-объектами. Penpot декомпозирует данные ClojureScript в бинарно упакованные структуры, записывает их в линейную память WASM и рендерит через 22 000-строчный Rust-движок. При отключённом WASM (по умолчанию) Penpot рендерит фигуры как SVG DOM-дерево. Для малых и средних документов прямой путь через CanvasKit быстрее. Тайловая система Penpot может выигрывать на очень больших канвасах (100K+ фигур), где видна лишь малая область — но накладные расходы значительны.
4. Граф сцены и модель данных
OpenPencil
// Плоская карта, O(1) поиск
nodes: Map<string, SceneNode>
// 29 типов узлов из Kiwi-схемы Figma
// ~390 полей на NodeChange (совместимо с Figma)- TypeScript-интерфейсы со строгой типизацией
- GUID соответствуют формату Figma
sessionID:localID - Прямой доступ к свойствам — без слоёв косвенности
Penpot
;; 20+ файлов определений типов в common/src/app/common/types/
;; shapes_builder.cljc, shapes_helpers.cljc
;; Отдельные системы типов для: color, component, container, fills,
;; grid, modifiers, objects_map, page, path и т.д.- Данные распределены по
common/(49 600 строк .cljc) - Отдельные модули геометрии для flex-макета (~6 файлов), grid-макета (~5 файлов), ограничений, границ, углов, эффектов
- Валидация схем в рантайме (Malli)
- Данные должны пересекать границу CLJS→Rust для рендеринга
Вердикт: модель данных
OpenPencil использует проверенную схему Figma (194 определения Kiwi) напрямую в TypeScript — без преобразований. Penpot поддерживает собственную систему типов в Clojure/ClojureScript/Rust, требующую ручной синхронизации между всеми тремя.
5. Движок макетов
OpenPencil: Yoga WASM (314 строк)
import Yoga from 'yoga-layout'
// Прямое сопоставление: поля Figma stack* → свойства Yoga flex
const root = Yoga.Node.create()
root.setFlexDirection(FlexDirection.Row)
root.calculateLayout()
applyYogaLayout(graph, frame, yogaRoot)314 строк всего. Синхронно, внутри процесса.
Penpot: двойная реализация
- ClojureScript (common):
flex_layout/(6 файлов),grid_layout/(5+ файлов) — собственные реализации - Rust WASM:
flex_layout.rs(741 строка),grid_layout.rs(843 строки) — реализовано с нуля
Penpot поддерживает два независимых движка макетов (CLJS и Rust), которые должны давать идентичные результаты.
Вердикт: макет
OpenPencil делегирует проверенной библиотеке (Yoga, используется React Native на миллиардах устройств) в 314 строках. Penpot поддерживает ~3 000+ строк собственного кода макета, дублированного на двух языках.
6. Формат файлов и совместимость с Figma
OpenPencil
- Нативный бинарный формат Kiwi — та же сериализация, что использует Figma внутри
- Прямой импорт файлов
.figчерез извлечённый Kiwi-кодек (2 178 строк схемы + 551 строка кодека) - Поддержка вставки из буфера обмена Figma (чтение бинарного Kiwi из буфера обмена)
- Совместимость на уровне протокола с мультиплеерным протоколом Figma
Penpot
- ZIP-архив (файлы
.penpot), содержащий JSON-манифесты, JSON-данные по файлам, бинарные ассеты и миниатюры (формат v3) - SVG используется для рендеринга по умолчанию и экспорта (опциональный WASM-рендерер доступен)
- Нет нативного импорта
.fig - Три версии формата (v1 legacy Transit, v2, v3 JSON-in-ZIP) с системой миграции
Вердикт: формат файлов
OpenPencil имеет значительное преимущество — он может читать файлы Figma нативно и даже вставлять данные из буфера обмена Figma. Penpot требует ручного экспорта/импорта и не может открывать файлы .fig.
7. Управление состоянием и отмена
OpenPencil
// 110 строк — паттерн обратных команд
class UndoManager {
apply(entry: UndoEntry) { entry.forward(); this.undoStack.push(entry) }
undo() { entry.inverse(); this.redoStack.push(entry) }
}110 строк. Forward/inverse замыкания, захватывающие минимальное состояние. Поддержка группировки для многоэтапных операций.
Penpot
Управление состоянием использует Potok (Redux-подобная библиотека для ClojureScript atoms). События реализуют UpdateEvent (чистое state→state) или WatchEvent (побочные эффекты через RxJS). Отмена хранит обратные векторы изменений (максимум 50 записей), с транзакциями для группировки быстрых изменений и авто-истечением через 20 секунд.
Вердикт: состояние
Подход OpenPencil проще и с меньшими накладными расходами. Подход Penpot больше подходит для совместной работы (изменения сериализуемы), но ценой сложности.
8. Опыт разработчика
| Метрика | OpenPencil | Penpot |
|---|---|---|
| Настройка среды | bun install && bun dev | Docker Compose + JVM + Node + Rust toolchain |
| Горячая перезагрузка | Vite HMR (~50мс) | shadow-cljs (секунды) |
| Проверка типов | TypeScript (строгий) | В рантайме (схемы Malli) |
| Время сборки | <5с (Vite) | Минуты (запуск JVM + компиляция CLJS + Rust WASM) |
| Порог первого вклада | Низкий (TS/Vue) | Высокий (Clojure + Rust + Docker) |
| Десктоп | Tauri v2 (~5 МБ) | Н/Д (только браузер) |
| Пул разработчиков | Огромный (TS/Vue) | Крошечный (ClojureScript + Rust) |
9. Характеристики производительности
| Сценарий | OpenPencil | Penpot |
|---|---|---|
| Холодный старт | <2с (загрузка WASM) | 10с+ (сервер + клиент + WASM) |
| Задержка операций | <1мс (в процессе) | 10-50мс (сетевой round-trip) |
| Кадр рендеринга | Прямой вызов Skia | CLJS→JS→WASM FFI→Skia |
| Базовый расход памяти | ~50 МБ (вкладка браузера) | ~300 МБ+ (JVM + Postgres + Valkey + браузер) |
| Работа офлайн | Полная (локальный подход) | Нет (зависимость от сервера) |
| Рендеринг 10K фигур | Один проход, без кеширования | Тайловый с 11 поверхностями |
10. В чём Penpot лучше
- Серверная совместная работа — централизованное многопользовательское редактирование с WebSockets, учётными записями и контролем доступа (OpenPencil использует P2P через Trystero + Yjs — без сервера, но и без контроля доступа или сохранения за пределами сессии)
- Экспорт PDF — headless Chromium для серверного PDF-рендеринга (OpenPencil экспортирует SVG, но пока не PDF)
- Система плагинов — полный API плагинов с изолированным выполнением
- Дизайн-токены — нативная поддержка дизайн-токенов
- Макет CSS Grid — собственная реализация (OpenPencil ждёт Yoga Grid)
- Самостоятельный хостинг — развёртывание на Docker для команд
- Зрелость — годы использования в продакшене, проверенный масштабированием
11. Скриптинг и расширяемость
OpenPencil поставляется с командой eval, предоставляющей Figma-совместимый Plugin API для headless-скриптинга — пакетные операции, автоматизированное тестирование и ИИ-модификации работают без GUI. Кроме того, 90 ИИ-инструментов доступны через встроенный чат, MCP-сервер (stdio + HTTP) и CLI — охватывающие чтение, создание, модификацию, структуру, переменные, векторные пути, анализ (цвета/типографика/отступы/кластеры), сравнение, булевые операции и упорядочивание. Penpot имеет систему плагинов с изолированным выполнением, но без headless-скриптинга или интеграции MCP.
Итоги
| Аспект | Лидер | Почему |
|---|---|---|
| Простота архитектуры | OpenPencil | Один процесс vs 5+ сервисов |
| Производительность рендеринга | OpenPencil | Прямой CanvasKit vs SVG DOM (по умолчанию) или бинарно упакованный WASM |
| Поддерживаемость кода | OpenPencil | ~26K строк на 1 языке vs 299K на 4+ языках |
| Совместимость с Figma | OpenPencil | Нативный Kiwi-кодек vs отсутствие поддержки .fig |
| Онбординг разработчиков | OpenPencil | TS/Vue vs Clojure/Rust/Docker |
| Десктоп | OpenPencil | Нативный Tauri vs только браузер |
| Движок макетов | OpenPencil | Yoga (проверенный) vs собственная двойная реализация |
| Совместная работа | Ничья | Penpot: серверная с контролем доступа; OpenPencil: P2P через Trystero + Yjs, без хостинга |
| Самостоятельный хостинг | Penpot | Docker-ready vs только десктоп |
| Зрелость экосистемы | Penpot | Годы продакшена vs ранняя стадия |
OpenPencil архитектурно легче — однопроцессный CanvasKit-рендерер в ~26K строках TypeScript, совместимый с Figma по дизайну. Penpot — полноценная платформа с ~299K строками на Clojure, ClojureScript, Rust и SCSS, плюс парк Docker-сервисов. Оба предлагают совместную работу в реальном времени (с разными архитектурами: P2P vs сервер). Penpot имеет экосистему плагинов и серверный экспорт PDF; OpenPencil имеет Figma-совместимый headless-скриптинг, 90 ИИ/MCP инструментов, экспорт SVG и нативное десктоп-приложение.