documentation
← Retour au playground

HTSL — documentation

HTSL (HyperText Structured Language) est un langage de balisage léger, pensé pour les mathématiciens et physiciens : une syntaxe concise qui compile en HTML, avec des formules (KaTeX), des graphes et des scènes 2D/3D animées (Plotly, Three.js), des composants réutilisables — le tout déclaratif et rendu par un runtime unique.

{h1:Théorème de Pythagore}
{p:Dans un triangle rectangle : {@mti: a^2 + b^2 = c^2}.}
{@plot[fn="sin(x)/x", xrange="(-15,15)", title="sinc"]/}

Philosophie

Trois principes guident tout le moteur :

  • Tout est objet structuré. Balises, formules, géométrie, composants partagent le même AST. Les objets {@…} sont décrits dans un registre introspectable : l'autocomplétion et la palette du playground en sont générées — rien n'est codé en dur.
  • Sortie 100 % déclarative. Le rendu ne produit jamais de <script> exécutable : chaque élément dynamique est un nœud de données (class="htsl-<type>" data-htsl-<type>="…").
  • Un runtime unique. Une seule couche JS interprète ces données : elle charge les dépendances (KaTeX/Plotly/Three.js) une fois, hydrate de façon idempotente, et nettoie les ressources.

Pourquoi ces choix (motivations)

Déclaratif + runtime

Émettre des <script> impératifs causait trois bugs structurels : code lancé avant le CDN, ré-exécuté aux rerenders, ou avant que le DOM soit prêt. Un runtime idempotent les élimine par construction : il attend ses dépendances, ne redessine que ce qui change (react), et purge ce qui disparaît.

Zéro JS depuis le contenu

Le contenu HTSL ne peut pas produire de JavaScript exécutable (un {script: …} inline est rendu inerte). On évite toute une classe de problèmes de sécurité et de cycle de vie ; le dynamisme passe par des objets déclaratifs.

Introspection

Le registre d'objets est la source unique de vérité. Palette, autocomplétion, aide contextuelle, et même ce catalogue sont générés depuis registry.list()/describe() — donc toujours à jour.

Iframe isolée

Le rendu vit dans une iframe : on peut y charger Tailwind, Bootstrap, KaTeX, Plotly… sans jamais polluer l'interface, et le morphing chirurgical (data-htsl-hash + morphdom) ne touche que les nœuds réellement modifiés.

Les outils derrière

Chaque dépendance est chargée à la demande, une seule fois, par le runtime — uniquement quand un document en a besoin.

KaTeX Formules

Rendu typographique des mathématiques (rapide, sans serveur). Repli sur le LaTeX brut si absent.

Plotly Graphes & géométrie

Graphes de fonctions 2D et scènes géométriques 2D/3D (repères, cercles, sphères, surfaces mathématiques). Mise à jour en place via Plotly.react.

Three.js 3D animé (WebGL)

Scènes 3D libres : formes, vecteurs, trajectoires, surfaces z=f(x,y), labels, et une timeline d'animations (dont un vrai morph de géométrie).

CodeMirror 6 Éditeur

Coloration HTSL (et JS dans {script}), autocomplétion contextuelle, indentation, linter — réutilisables via le paquet @noah-medra/htsl-codemirror.

morphdom Rendu chirurgical

Seuls les nœuds réellement changés sont mis à jour ; un bloc au hash inchangé n'est jamais re-rendu (ni re-KaTeX, ni re-Plotly).

Tailwind / Bootstrap Mise en page

Chargés depuis le document HTSL (CDN dans l'iframe). Le style appartient au document, pas au moteur.

Syntaxe de base

  • Élément : {tag: contenu} · classes {tag.box: …} · id {tag#main: …}
  • Attributs entre crochets, séparés par des virgules, chaînes entre guillemets doubles : {a[href="/x", target="_blank"]: lien}
  • Auto-fermant : {img[src="a.png"]/}
  • Commentaire : {!-- ignoré --} · Échappements : \{ \} \: \$
{div.card[class="p-4 rounded-xl bg-white"]:
  {h2:Titre}
  {p:Un paragraphe avec du {strong:texte fort}.}
}

Mathématiques

  • En ligne : {@mti: a^2+b^2} ou $a^2+b^2$ — Bloc : {@mtb: …} ou $$…$$
  • Équation numérotée : {@mte[label=euler]: e^{i\pi}+1=0} — Référence : {@mtr[to=euler]/}
  • Aligné / cas / système : {@mta: {line:…}}, {@mtc[intro="…"]: {case:…}}, {@mts: {line:…}} · Fraction {@mof:{num:1}{den:2}}
{@mte[label=ham]: H = \tfrac{1}{2}\big(p^2 + \omega^2 q^2\big)}
{p:Le hamiltonien {@mtr[to=ham]/} décrit l'énergie totale.}

Composants & variables

{!define carte[titre, couleur=indigo]:
  {div[class="card text-{$couleur}-600"]:
    {h3:{$titre}}
    {div.body:{$children}}
  }
}
{@carte[titre="Bonjour"]: contenu injecté}

{!set vitesse: 9.81}
{!set H: "\tfrac{1}{2}\big(p^2 + \omega^2 q^2\big)"}   {!-- LaTeX verbatim (guillemets) --}
{p:g = {$vitesse} m/s², {@mtb: H = {$H}}}

Astuce. Une valeur de {!set} entre guillemets est prise verbatim : ses { } et \ ne sont pas interprétés — idéal pour réutiliser des macros LaTeX (hamiltoniens, opérateurs…).

Graphes 2D

Une fonction : {@plot[fn="sin(x)/x", xrange="(-15,15)", title="…"]/}. Plusieurs courbes avec légende : {@plot} devient un conteneur.

{@plot[xrange="(-6.28,6.28)", title="Trigonométrie"]:
  {@plot.curve[fn="sin(x)", label="sin"]/}
  {@plot.curve[fn="cos(x)", label="cos"]/}
}

Les expressions (sin(x)/x, cos(x)…) sont évaluées par un interpréteur sûr (pas de eval) : opérateurs + - * / % ^, fonctions usuelles, constantes pi e tau phi.

Géométrie (Plotly)

{@mg2.scene:
  {@mg2.frame[grid=true]/}
  {@mg2.circle[center="(0,0)", radius=2]/}
  {@mg2.point[x=0, y=0, name=O]/}
}
{@mg3.scene:
  {@mg3.space[grid=true]/}
  {@mg3.sphere[center="(0,0,0)", radius=2]/}
}

Scènes 3D animées (Three.js)

Formes, vecteurs, lignes/trajectoires, surfaces z=f(x,y), labels, repère/grille, contrôles souris — et des animations par id.

{@s3.scene[height=440, controls=true, distance=9]:
  {@s3.axes[size=3]/}  {@s3.grid[size=12]/}
  {@s3.box[id="A", color="#f472b6"]/}
  {@s3.torus[id="B", x=99, color="#34d399"]/}     {!-- gabarit hors champ --}
  {@s3.surface[z="sin(x)*cos(y)", xrange="(-3,3)", yrange="(-3,3)"]/}
  {@s3.vector[from="(0,0,0)", to="(2,2,1)"]/}
  {@s3.animate[target="A", action="move", to="(2,2,0)", duration=2]/}
  {@s3.animate[target="A", action="transform", to="B", duration=2]/}   {!-- vrai morph de forme --}
}

Actions : move, rotate, scale, color, fade, transform (morph de géométrie réel + couleur). Options : duration, delay, at, easing ; la scène loope par défaut.

Frameworks CSS

Le rendu est une iframe isolée : chargez n'importe quel framework depuis votre document. Les classes passent par [class="…"] (les variantes hover:, md:, w-1/2 ne rentrent pas dans le raccourci .classe).

{link[rel="stylesheet", href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"]/}
{script[src="https://cdn.tailwindcss.com"]/}

{div[class="bg-white ring-1 ring-slate-200 rounded-xl p-4 hover:shadow-md"]:Carte Tailwind}

Éditer depuis le rendu

  • Texte : cliquez un texte pour le corriger en place (sans syntaxe).
  • Bloc : double-cliquez un bloc parent → un vrai éditeur HTSL (coloration, autocomplétion) s'ouvre translucide par-dessus. ⌘/Ctrl+Entrée valide, Échap annule.

Sécurité

  • Échappement HTML par défaut de tout texte et de toute valeur d'attribut (prévention XSS).
  • Le contenu HTSL ne produit jamais de JS exécutable : un {script: …code…} inline est rendu inerte (type="text/plain"). Seul {script[src=…]/} (CDN choisi par l'auteur) charge du code.

Intégration — utiliser HTSL hors du playground

Le moteur est distribué en deux paquets npm publics — @noah-medra/htsl-core (le moteur + le runtime) et @noah-medra/htsl-codemirror (l'éditeur, optionnel) — plus un bundle CDN. L'architecture est toujours la même : compile() produit du HTML inerte (des nœuds data-htsl-*, jamais de <script> exécutable), puis un runtime unique hydrate la page (KaTeX/Plotly/Three.js chargés à la demande). Trois façons de l'exploiter, du plus simple au plus intégré.

1 · Sans rien installer — balise <script> (CDN)

Idéal pour une page statique, un blog, un cours. Un seul script : il expose le global htsl_engine et installe le runtime (window.HTSL), qui hydrate automatiquement ce que tu injectes.

<script src="https://unpkg.com/@noah-medra/htsl-core/dist-min/htsl.auto.global.js"></script>
<div id="out"></div>
<script>
  document.getElementById("out").innerHTML =
    htsl_engine.compile("{h1:Bonjour} {@mte: E = mc^2} {@plot[fn=\"sin(x)/x\", xrange=\"(-15,15)\"]/}");
  // le runtime (MutationObserver) hydrate la formule et le graphe tout seul
</script>

Variantes disponibles dans dist-min/ : htsl.global.js (même global, sans auto-runtime — tu appelles htsl_engine.installHtslRuntime() toi-même) et htsl.min.js (ESM minifié pour un import par URL). Remplace unpkg.com par cdn.jsdelivr.net/npm si tu préfères jsDelivr.

2 · Dans une application — npm

npm install @noah-medra/htsl-core
import { compile, installHtslRuntime } from "@noah-medra/htsl-core";

// 1) compiler du HTSL en HTML (nœuds de données, inertes)
document.querySelector("#doc").innerHTML = compile(
  "{h2:Trajectoire} {@plot[fn=\"cos(x)*exp(-x*x/20)\", xrange=\"(-10,10)\"]/}"
);

// 2) installer le runtime une fois : il hydrate les data-htsl-* présents
//    (et à venir), en chargeant KaTeX/Plotly/Three uniquement si nécessaire
installHtslRuntime();

API utile : parse() (HTSL → AST), render(ast, opts) (AST → HTML, options prettyPrint/allowedTags), compile() (les deux), mathCss (styles des équations), registry (introspection des objets), HTSLError. Le paquet est en ESM, typé (TypeScript), sans dépendance dans le cœur.

3 · Côté serveur / build — HTML statique

Pour générer des fichiers (polycopiés, site statique, CI), compile au build et sers le HTML. Ajoute le runtime côté client uniquement si le document contient du dynamique (formules, graphes, scènes).

import { compile, mathCss } from "@noah-medra/htsl-core";
import { writeFileSync } from "node:fs";

const body = compile(sourceHtsl, { prettyPrint: true });
writeFileSync("sortie.html", `<!doctype html><html><head>
  <style>${mathCss}</style>
</head><body>${body}
  <script src="https://unpkg.com/@noah-medra/htsl-core/dist-min/htsl.auto.global.js"></script>
</body></html>`);

Bonus · un éditeur HTSL dans ton app

Le même éditeur que ce playground (coloration, autocomplétion, indentation, linter, pliage) est réutilisable via @noah-medra/htsl-codemirror sur CodeMirror 6.

npm install @noah-medra/htsl-codemirror @noah-medra/htsl-core codemirror
import { EditorView, basicSetup } from "codemirror";
import { autocompletion } from "@codemirror/autocomplete";
import { parse, registry } from "@noah-medra/htsl-core";
import { htslLanguage, htslCompletion, htslLinter } from "@noah-medra/htsl-codemirror";

new EditorView({
  parent: document.querySelector("#editor"),
  doc: "{div.box:Bonjour {@mti: x^2}}",
  extensions: [
    basicSetup,
    htslLanguage(),
    autocompletion({ override: [htslCompletion(registry)] }),
    htslLinter(parse),
  ],
});

Paquets & liens. @noah-medra/htsl-core · @noah-medra/htsl-codemirror · code source sur GitHub. Sécurité : où qu'il s'exécute, le moteur n'émet jamais de JS depuis le contenu (voir Sécurité).

Catalogue d'objets

Généré en direct depuis l'introspection du moteur (registry.list()) — donc toujours à jour.

Prompt pour une IA

Collez ce prompt à n'importe quel modèle pour qu'il sache rédiger des documents HTSL automatiquement. Il inclut la syntaxe, les contraintes et le catalogue complet.