Back to flin
flin

Le moteur de réactivité : comment FLIN rend tout réactif

Le moteur de réactivité de FLIN : suivi automatique des dépendances, mises à jour basées sur SSE et rendu incrémental du DOM.

Juste A. Gnimavo (Thales) & Claude | March 26, 2026 6 min flin
EN/ FR/ ES
flinreactivitysseincrementaldomreactive-programming

La réactivité est l'âme d'un framework web moderne. Quand les données changent, l'interface utilisateur devrait se mettre à jour. Cela semble simple. Ce ne l'est pas.

React le résout avec un DOM virtuel et un algorithme de diffing. Svelte le résout avec un suivi des dépendances au moment de la compilation. Vue le résout avec des proxies et un système d'effets réactifs. Angular le résout avec la détection de changements et les zones. Chaque approche a des compromis en complexité, performance et expérience développeur.

FLIN le résout différemment. La réactivité n'est pas une fonctionnalité de framework -- c'est une fonctionnalité de runtime, intégrée dans la VM et la couche serveur. Vous écrivez count = count + 1, et chaque {count} dans la vue se met à jour. Pas d'abonnements manuels. Pas de tableaux de dépendances. Pas de hooks useEffect.


Les trois couches de réactivité

Le système de réactivité de FLIN opère à trois couches :

  1. Rendu côté serveur : la VM exécute les opcodes de vues et produit du HTML avec des annotations réactives.
  2. Proxy côté client : un Proxy JavaScript intercepte les affectations de variables et déclenche les mises à jour du DOM.
  3. Mises à jour envoyées par le serveur : SSE pousse les changements de données du serveur au navigateur en temps réel.

Chaque couche gère un aspect différent de la réactivité. Ensemble, elles créent un système où les changements se propagent instantanément, qu'ils proviennent de l'interaction utilisateur (côté client), de la logique serveur (SSE) ou des mutations de base de données (abonnements aux entités).


Couche 1 : annotations réactives

Quand la VM rend une vue, elle marque le contenu dynamique avec des attributs data-flin-bind. Le texte statique est rendu directement. Les interpolations dynamiques ({name}, {count}) sont enveloppées dans des éléments <span> avec des attributs data-flin-bind. La valeur de l'attribut est l'expression à réévaluer quand l'état change.

Cette approche par annotations a deux avantages par rapport au diffing de DOM virtuel :

Précision. Le runtime sait exactement quels noeuds DOM doivent être mis à jour. Il n'a pas besoin de différer l'arbre entier -- il interroge [data-flin-bind] et met à jour seulement ces éléments.

Simplicité. La mise à jour réactive entière est une requête DOM plus une affectation de contenu texte. Pas de réconciliation d'arbre. Pas de planification de fibers. Pas de mode concurrent.


Couche 2 : le proxy réactif

Le runtime côté client enveloppe l'état de l'application dans un Proxy JavaScript :

javascriptconst _state = { count: 0, name: "Thales" };

const $flin = new Proxy(_state, {
    set(target, property, value) {
        target[property] = value;
        _scheduleUpdate();
        return true;
    }
});

Chaque variable d'état est exposée comme une propriété globale avec un getter et un setter. Quand un setter est appelé (par exemple count++ dans un gestionnaire d'événement), le piège set du Proxy se déclenche, met à jour l'état sous-jacent et planifie une mise à jour du DOM.


Couche 2.5 : batching des mises à jour

FLIN regroupe les mises à jour en utilisant requestAnimationFrame :

javascriptlet _updateScheduled = false;

function _scheduleUpdate() {
    if (!_updateScheduled) {
        _updateScheduled = true;
        requestAnimationFrame(function() {
            _flinUpdate();
            _updateScheduled = false;
        });
    }
}

function _flinUpdate() {
    document.querySelectorAll('[data-flin-bind]').forEach(function(el) {
        const expr = el.getAttribute('data-flin-bind');
        try {
            const value = eval(expr);
            if (el.textContent !== String(value)) {
                el.textContent = value;
            }
        } catch (e) {
            // L'évaluation de l'expression a échoué -- laisser le contenu inchangé
        }
    });
}

Le drapeau _updateScheduled assure qu'un seul callback requestAnimationFrame est en file d'attente à la fois. Plusieurs changements d'état dans le même gestionnaire d'événement résultent en une seule mise à jour du DOM au prochain cadre d'animation.

La vérification if (el.textContent !== String(value)) évite les écritures DOM inutiles. Si la valeur n'a pas réellement changé, le noeud DOM n'est pas touché. Cela empêche le thrashing de mise en page et les repeintures inutiles.


Couche 3 : Server-Sent Events

La couche SSE gère la réactivité qui provient du serveur. Quand le serveur détecte un changement d'état (une mutation de base de données, une complétion de tâche de fond, un déclenchement de timer), il pousse la mise à jour au navigateur via la connexion SSE.

L'endpoint SSE (/_sse) sert deux objectifs dans FLIN :

  1. HMR : pousser des événements de rechargement quand les fichiers source changent.
  2. Mises à jour de données : pousser des notifications de changement d'entité quand la base de données est modifiée.

Le pipeline de réactivité

En mettant les trois couches ensemble, le pipeline de réactivité complet ressemble à ceci :

L'utilisateur clique sur un bouton
    -> Le gestionnaire d'événement s'exécute (count++)
    -> Le piège set du Proxy se déclenche
    -> _scheduleUpdate() mis en file d'attente
    -> requestAnimationFrame se déclenche
    -> _flinUpdate() s'exécute
    -> Tous les éléments [data-flin-bind] réévalués
    -> Éléments changés mis à jour

Le serveur sauvegarde une entité
    -> FlinDB notifie les observateurs d'entités
    -> SSE diffuse un événement entity_change
    -> Le client reçoit l'événement SSE
    -> Le gestionnaire d'entité se déclenche
    -> _flinUpdate() s'exécute
    -> Éléments changés mis à jour

Trois points d'entrée (interaction utilisateur, logique serveur, mises à jour distantes), un point de sortie (_flinUpdate). Cette convergence simplifie le débogage : si une liaison ne se met pas à jour, le problème est soit dans le point d'entrée (l'événement ne se déclenche pas), soit dans la fonction de mise à jour (l'expression ne s'évalue pas correctement). Il n'y a pas de graphe de dépendances complexe à tracer.


Le principe de conception

Le moteur de réactivité incarne le principe de conception fondamental de FLIN : rendre le cas courant automatique et le programmeur invisible.

Dans React, le développeur doit appeler useState, déstructurer l'état et le setter, appeler le setter pour déclencher un re-rendu, et gérer le tableau de dépendances de useEffect pour éviter les fermetures périmées. Dans FLIN, le développeur écrit count = 0 et {count}. Le système de réactivité gère tout le reste.

Ce n'est pas une préférence philosophique. C'est une décision pratique ancrée dans le public cible de FLIN : les développeurs en Cote d'Ivoire et à travers l'Afrique de l'Ouest qui veulent construire des applications web rapidement, sans maîtriser les subtilités des hooks React ou de la syntaxe $: de Svelte. Le système de réactivité devrait être invisible parce que le développeur a des choses plus importantes auxquelles penser -- comme l'application qu'il construit.


Ceci est la partie 28 de la série « Comment nous avons construit FLIN », documentant comment un CEO à Abidjan et un CTO IA ont construit un langage de programmation à partir de zéro.

Prochain : [29] Le premier rendu dans le navigateur -- le moment marquant où le code FLIN a compilé en HTML et est apparu dans Chrome pour la première fois.

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

Le Step Zero ne suffisait pas : comment valider un constructeur sans valider le runtime a fait tomber toutes les sessions vocales de Déblo l’heure où nous avons livré le streaming caméra temps réel

La phase 14 a livré Déblo Eyes — streaming caméra temps réel via LiveKit vers Gemini Live native audio. Le premier deploy a fait tomber toutes les sessions vocales en production en quatre-vingt-dix secondes parce que notre Step 0 avait validé le constructeur sans exercer le runtime. Le build log de comment Déblo a eu des yeux, ce qu’un pré-vol incomplet a coûté, et quels points de polish ont été livrés ou reportés.

33 min May 20, 2026
debloclaude-opus-4.7claude-codegemini-live +25
Thales & Claude deblo

Le tiret cadratin qui a tué la production : comment un slogan marketing dans un header HTTP a fait tomber le chat de Déblo pendant 24 heures

Deux jours avant la soumission App Store, tout le produit chat de Déblo s’est cassé silencieusement. Pas de spinner, pas de toast, aucune erreur dans l’UI — juste un silence radio. L’incident de 24 heures se résumait à un seul « é » dans la valeur d’un header HTTP qui levait une UnicodeEncodeError avant qu’aucune requête vers OpenRouter ne quitte le backend. Post-mortem d’une fausse hypothèse, d’une trace Sentry, et d’un fix de six lignes qui a débloqué le lancement.

30 min May 19, 2026
debloclaude-opus-4.7claude-codeincident +19
Thales & Claude deblo

Six heures, d’une page blanche à la review Apple — Comment nous avons soumis Déblo à l’App Store, en direct

Marche à marche en direct de la soumission de Déblo à l’App Store iOS en six heures : ce que les validateurs d’Apple ont rejeté (un superscript Unicode), ce que nous avons corrigé (un Promotional Text gaspillé sur des marques tierces), et les rouages de l’ASO iOS que presque tout le monde rate.

30 min May 13, 2026
debloclaude-opus-4.7claude-codeapp-store +16