# Open Pencil vs Penpot : Comparaison d'architecture et de performances Pourquoi comparer ? OpenPencil existe parce que les plateformes de design fermées contrôlent ce qui est possible. Comprendre les différences architecturales montre ce qu'une alternative ouverte et local-first peut faire différemment. ::: info Renderer WASM de Penpot Penpot 2.x inclut un renderer Rust/Skia WASM (`render-wasm/v1`) activable via les flags du serveur ou le paramètre URL `?wasm=true`. Le renderer SVG reste le défaut. Cette page couvre les deux. ::: ## 1. Échelle et taille du code | Métrique | Open Pencil | Penpot | |----------|-------------|--------| | LOC total | **~26 000** | **~299 000** | | Fichiers source | ~143 | ~2 900 | | Langages | TypeScript, Vue | Clojure, ClojureScript, Rust, JS, SQL, SCSS | | Moteur de rendu | ~3 200 LOC (TS, 10 plików) | 22 000 LOC (Rust/Skia WASM) | | Code UI | ~4 500 LOC | ~175 000 LOC (CLJS + SCSS) | | Backend | Aucun (local-first) | 32 600 LOC + 151 fichiers SQL | | Ratio LOC | **1×** | **~11×** | Open Pencil est **~11× plus petit** — et c'est tout l'intérêt. Ce n'est pas une simplification ; c'est une architecture fondamentalement différente. ## 2. Architecture ### Open Pencil : Client monolithique ``` ┌─────────────────────────────────┐ │ Tauri (shell natif) │ │ ┌───────────────────────────┐ │ │ │ Vue 3 + TypeScript │ │ │ │ ┌─────────┐ ┌──────────┐│ │ │ │ │ Editor │ │ Kiwi ││ │ │ │ │ Store │ │ Codec ││ │ │ │ └────┬─────┘ └──────────┘│ │ │ │ │ │ │ │ │ ┌────▼────────────────┐ │ │ │ │ │ Scene Graph (TS) │ │ │ │ │ │ Map │ │ │ │ │ └────┬────────────────┘ │ │ │ │ │ │ │ │ │ ┌────▼────┐ ┌──────────┐│ │ │ │ │ Skia │ │ Yoga ││ │ │ │ │CanvasKit│ │ Layout ││ │ │ │ │ (WASM) │ │ (WASM) ││ │ │ │ └─────────┘ └──────────┘│ │ │ └───────────────────────────┘ │ └─────────────────────────────────┘ ``` **Tout dans un seul processus.** Pas de serveur, pas de base de données, pas de Docker. ### Penpot : Client-serveur distribué ``` ┌───────────────────────────────────────────────────────┐ │ Docker Compose │ │ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐ │ │ │ Frontend │ │ Backend │ │ Exporter │ │ │ │ ClojureScript│ │ Clojure │ │ (Chromium) │ │ │ │ shadow-cljs │ │ JVM │ │ │ │ │ │ ┌─────────┐ │ │ ┌────────┐ │ └──────────────┘ │ │ │ │render- │ │ │ │Postgres│ │ │ │ │ │wasm │ │ │ │Valkey │ │ ┌──────────────┐ │ │ │ │(Rust→ │ │ │ │ MinIO │ │ │ MCP │ │ │ │ │ Skia │ │ │ └────────┘ │ │ Server │ │ │ │ │ WASM) │ │ │ │ └──────────────┘ │ │ │ └─────────┘ │ │ │ │ │ └──────────────┘ └─────────────┘ │ └───────────────────────────────────────────────────────┘ ``` **Minimum 5+ services.** PostgreSQL, Redis (Valkey), MinIO, un backend JVM, un exporteur Node.js (Chromium headless), plus le frontend ClojureScript. ### Verdict : Architecture L'architecture monoprocessus d'Open Pencil élimine : la latence réseau, l'overhead de sérialisation aux frontières de services, la complexité d'orchestration de conteneurs et l'overhead de requêtes base de données. ## 3. Pipeline de rendu ### Open Pencil : TS → CanvasKit WASM (direct) ```typescript renderSceneToCanvas(canvas, graph, pageId) { this.fillPaint.setColor(...) canvas.drawRRect(rrect, this.fillPaint) } ``` - **1 franchissement de frontière :** TS → WASM (CanvasKit) - Le graphe de scène vit dans le heap JS — pas de sérialisation pour le rendu - 1 646 LOC de renderer au total ### Penpot : JS (compilé depuis CLJS) → Rust WASM → Skia ``` ClojureScript (compilé en JS) → décomposer en primitives + empaqueter en binaire en mémoire linéaire WASM → Rust WASM (via Emscripten C FFI) → skia-safe (bindings Rust Skia) → Skia (WebGL) ``` Quand désactivé (défaut), les formes sont rendues comme un arbre DOM SVG. Quand activé, système de rendu par tuiles avec 11 surfaces et cache de 1024 entrées. ### Verdict : Rendu | Aspect | Open Pencil | Penpot | |--------|-------------|--------| | Frontière JS→WASM | Direct (objets TS) | Empaquetage binaire (104 octets par forme) | | Modèle de rendu | Immédiat/redessin complet | Cache par tuiles | | Gestion des surfaces | 1 surface | 11 surfaces | | Overhead mémoire | Faible (pas de cache tuiles) | Élevé (1024 entrées cache) | | Complexité du code | 1 646 LOC | 22 000 LOC | | Code unsafe | Aucun | État global `unsafe` | ## 4. Graphe de scène et modèle de données ### Open Pencil ```typescript nodes: Map // 29 types de nœuds du schéma Kiwi de Figma // ~390 champs par NodeChange (compatible Figma) ``` - Interfaces TypeScript avec types stricts - GUIDs au format `sessionID:localID` de Figma ### Penpot - Données réparties dans `common/` (49 600 LOC de .cljc) - Validation de schéma au runtime (Malli) - Les données doivent traverser la frontière CLJS→Rust pour le rendu ### Verdict : Modèle de données Open Pencil réutilise le schéma éprouvé de Figma directement en TypeScript — zéro traduction. Penpot maintient son propre système de types à travers trois langages. ## 5. Moteur de layout ### Open Pencil : Yoga WASM (314 LOC) ```typescript import Yoga from 'yoga-layout' const root = Yoga.Node.create() root.setFlexDirection(FlexDirection.Row) root.calculateLayout() ``` 314 lignes au total. Synchrone, dans le processus. ### Penpot : Double implémentation Penpot maintient **deux moteurs de layout indépendants** (CLJS et Rust) qui doivent produire des résultats identiques. ~3 000+ LOC de code layout personnalisé dupliqué. ### Verdict : Layout Open Pencil délègue à Yoga (utilisé par React Native sur des milliards d'appareils) en 314 lignes. ## 6. Format de fichier et compatibilité Figma ### Open Pencil - **Format binaire Kiwi natif** — même sérialisation que Figma - Import direct `.fig`, collage depuis le presse-papiers Figma - Compatible avec le protocole multiplayer de Figma ### Penpot - **Archive ZIP** (`.penpot`) avec manifestes JSON et assets binaires - Pas d'import natif `.fig` - Trois versions de format avec système de migration ### Verdict : Format de fichier Open Pencil a un avantage significatif — il peut lire les fichiers Figma nativement et coller les données du presse-papiers Figma. ## 7. Gestion d'état et annulation ### Open Pencil ```typescript // 110 LOC — patron de commande inverse class UndoManager { apply(entry) { entry.forward(); this.undoStack.push(entry) } undo() { entry.inverse(); this.redoStack.push(entry) } } ``` 110 lignes. Closures forward/inverse avec état minimal. ### Penpot Gestion d'état via Potok (bibliothèque Redux-like pour atomes ClojureScript). Undo stocke des vecteurs de changements inverses (max 50 entrées), avec auto-expiration après 20 secondes. ## 8. Expérience développeur | Métrique | Open Pencil | Penpot | |----------|-------------|--------| | Setup dev | `bun install && bun dev` | Docker Compose + JVM + Node + Rust | | Rechargement à chaud | Vite HMR (~50ms) | shadow-cljs (secondes) | | Vérification de types | TypeScript (strict) | Runtime (schémas Malli) | | Temps de build | <5s (Vite) | Minutes (JVM + CLJS + Rust WASM) | | Barrière première contribution | Basse (TS/Vue) | Haute (Clojure + Rust + Docker) | | Desktop | Tauri v2 (~5Mo) | N/A (navigateur uniquement) | | Bassin de recrutement | Massif (devs TS/Vue) | Petit (ClojureScript + Rust) | ## 9. Caractéristiques de performance | Scénario | Open Pencil | Penpot | |----------|-------------|--------| | Démarrage à froid | <2s (chargement WASM) | 10s+ (serveur + client + WASM) | | Latence d'opération | <1ms (dans le processus) | 10-50ms (aller-retour réseau) | | Frame de rendu | Appel Skia direct | CLJS→JS→WASM FFI→Skia | | Mémoire de base | ~50Mo (onglet navigateur) | ~300Mo+ (JVM + Postgres + Valkey + navigateur) | | Capacité hors ligne | Complète (local-first) | Aucune (dépend du serveur) | | Rendu 10K formes | Une passe, sans cache | Par tuiles avec 11 surfaces | ## 10. Ce que Penpot fait mieux 1. **Collaboration serveur** — édition multi-utilisateurs centralisée avec WebSockets, comptes et contrôle d'accès 2. **Export PDF serveur** — service d'export Chromium headless pour PDF (Open Pencil exporte déjà en SVG nativement) 3. **Système de plugins** — API complète avec exécution sandboxée 4. **Tokens de design** — support natif des design tokens 5. **CSS Grid layout** — implémentation personnalisée (Open Pencil attend Yoga Grid) 6. **Self-hosting** — déploiement Docker pour les équipes 7. **Maturité** — années d'utilisation en production ## 11. Scripting et extensibilité OpenPencil inclut une [commande `eval`](/programmable/cli/scripting) offrant une API Plugin compatible Figma pour le scripting headless. De plus, 90 outils IA sont disponibles via le chat intégré, le serveur MCP (stdio + HTTP) et le CLI. Penpot a un système de plugins avec exécution sandboxée mais pas d'API de scripting headless ni d'intégration MCP. ## Résumé | Dimension | Gagnant | Pourquoi | |-----------|---------|----------| | **Simplicité architecturale** | Open Pencil | Un processus vs 5+ services | | **Performance de rendu** | Open Pencil | CanvasKit direct vs SVG DOM (défaut) ou WASM empaqueté | | **Maintenabilité du code** | Open Pencil | ~26K LOC en 1 langage vs 299K en 4+ | | **Compatibilité Figma** | Open Pencil | Codec Kiwi natif vs pas de support .fig | | **Onboarding développeur** | Open Pencil | TS/Vue vs Clojure/Rust/Docker | | **Expérience desktop** | Open Pencil | Tauri natif vs navigateur uniquement | | **Moteur de layout** | Open Pencil | Yoga (éprouvé) vs double implémentation | | **Collaboration** | Égalité | Penpot : serveur avec contrôle d'accès ; Open Pencil : P2P via Trystero + Yjs | | **Self-hosting** | Penpot | Prêt Docker vs desktop uniquement | | **Maturité écosystème** | Penpot | Années de production vs stade précoce | Open Pencil est architecturalement plus léger — un renderer CanvasKit monoprocessus en ~26K LOC TypeScript, compatible Figma par conception. Penpot est une plateforme full-stack avec ~299K LOC. Open Pencil a le scripting headless, **90 outils AI/MCP**, export SVG et une app desktop native.