EnglishBac à sable

Templates html``

Fluixi permet d'écrire du balisage de deux façons : en JSX, ou avec un template balisé html . Les deux compilent vers les *mêmes* appels DOM à granularité fine : il n'y a aucune différence à l'exécution. html est une surface d'écriture sans JSX, pour quand vous ne voulez pas configurer JSX dans votre tsconfig.

import { html } from '@fluixi/core';

function Hello(props: { name: string }) {
  return html`<h1>Bonjour ${props.name}</h1>`;
}

Importez html (et svg) depuis @fluixi/core, ou depuis @fluixi/start dans une application méta-framework.

Comment ça compile

C'est la clé de tout ce qui suit. Le template n'est pas analysé à l'exécution. À la compilation, le compilateur le sépare en balisage statique et en trous, émet la partie statique une seule fois, et relie chaque trou à un appel DOM ciblé :

html`<p class="row">Compteur : ${count()}</p>`;

devient, en substance, un <p class="row"> construit une fois et un unique nœud texte tenu à jour par un effet. Rien n'est re-rendu, rien n'est comparé.

Deux conséquences à connaître :

  • La valeur d'un trou n'est jamais analysée comme du HTML. Elle est affectée comme texte, comme attribut ou comme propriété. Interpoler une chaîne contenant <script> insère ce texte, cela ne crée pas d'élément. La seule exception délibérée est html=, documentée comme une échappatoire pour cette raison même.
  • La forme du template est figée à la compilation. Un trou peut fournir une valeur, un enfant, un composant ou un ensemble de props — mais pas un nom de balise ni un nom d'attribut, car ceux-là déterminent la forme. Voir les composants pour le cas des balises dynamiques.

Interpolation

Tout ${…} est un trou réactif. Lire un signal à l'intérieur maintient cet endroit — et uniquement celui-là — à jour :

const [count, setCount] = createSignal(0);
html`<p>Compteur : ${count()}</p>`;

Où placer un trou

html`<p>${text()}</p>`;                    // enfant — texte, nœud, tableau, autre template
html`<div title=${label()}></div>`;        // valeur d'attribut entière
html`<div class="card ${theme()}"></div>`; // partie d'une valeur entre guillemets
html`<div ...${attrs}></div>`;             // un objet de props entier
html`<${Card} />`;                         // un composant en position de balise

Un trou qui remplit toute la valeur d'un attribut la transmet telle quelle : elle peut donc être de n'importe quel type — un nombre, un booléen, un objet pour class/style. Les guillemets n'y changent rien : value=${n} et value="${n}" compilent à l'identique.

Mêler du texte statique et un trou relève de l'interpolation de chaîne, et le résultat est toujours une chaîne :

html`<input value=${0} />`;                // le nombre 0
html`<div class="card ${theme()}"></div>`; // la chaîne « card dark »

Lire un signal, ne pas le passer

Appelez l'accesseur dans le trou. Le trou est lui-même la frontière réactive : le compilateur l'enveloppe dans un effet pour vous.

html`<p>${count()}</p>`;   // ✓ suivi — ne réexécute que ce nœud texte
html`<p>${count}</p>`;     // affiche la fonction, pas sa valeur

Composants

Deux formes, dont la différence ne tient qu'à la façon dont TypeScript voit le nom :

// Balise dynamique — `${Card}` est une vraie expression, TypeScript la compte comme utilisée.
html`<${Card} title=${t()} />`;

// Balise statique — nom nu, comme en JSX.
html`<Card title=${t()} />`;

La forme statique a besoin de quelque chose pour signaler à TypeScript que le nom est utilisé, puisqu'il n'est que du texte dans une chaîne : soit @fluixi/ts-plugin, soit une règle resolve qui fournit le composant sans import. Dans un projet TypeScript sans l'un ni l'autre, préférez <${Card}/>.

Les enfants et les balises fermantes fonctionnent comme attendu. Une balise dynamique se ferme avec la même expression :

html`<${Card}>
  <p>Contenu</p>
</${Card}>`;

html`<Card>
  <p>Contenu</p>
</Card>`;

L'auto-fermeture fonctionne pour les deux formes et — contrairement à HTML — pour n'importe quel composant.

Composants auto-importés

Les composants de contrôle de flux et du routeur se résolvent sans import : écrivez <Show>, <For>, <Router>, <Outlet> et le compilateur ajoute l'import.

html`
  <${Show} when=${user()} fallback=${html`<a href="/login">Se connecter</a>`}>
    <p>Bienvenue, ${user()!.name}</p>
  </${Show}>
`;

Vos propres composants et ceux d'une bibliothèque peuvent les rejoindre — voir résolution des composants.

Props

Chaque prop est un trou, et chacune est réactive indépendamment :

html`<${Row} label="Statique" count=${n()} onSelect=${pick} ...${rest} />`;

Les enfants arrivent dans props.children. Un enfant fonction est transmis tel quel : c'est ce sur quoi reposent <For> et each.

Templates imbriqués

Un trou peut contenir un autre template — pratique pour fallback, les éléments de liste ou les branches conditionnelles :

html`<ul>${items().map((i) => html`<li>${i.label}</li>`)}</ul>`;

Un template imbriqué compile comme les autres : construit une fois, réutilisé à chaque appel. Pour une liste qui change, préférez each ou <For>.map() reconstruit toutes les lignes à chaque changement, là où <For> réutilise celles dont la donnée n'a pas bougé.

SVG

Utilisez la balise svg pour placer un sous-arbre dans l'espace de noms SVG. Les éléments créés là en ont besoin, et la balise html ne l'applique pas :

import { svg } from '@fluixi/core';

svg`<circle cx="50" cy="50" r=${r()} />`;

Une racine <svg> écrite dans html fonctionne aussi — le compilateur reconnaît la balise et crée le sous-arbre dans le bon espace de noms. Réservez svg aux templates qui commencent en dessous de la racine, avec un <circle> ou un <path> sans ancêtre <svg> dans le même template pour le signaler.

Espaces et texte

Les espaces sont conservés tels quels, comme en HTML — repliés par CSS au rendu, pas par le compilateur. <pre>, <textarea>, <script> et <style> gardent leur contenu verbatim.

Prise en charge de l'éditeur

@fluixi/ts-plugin (inclus dans toute application créée par le scaffolding, et livré dans l'extension VS Code) donne à html `` une véritable IntelliSense : survol, complétion des attributs d'élément et des props de composant, aller à la définition sur les balises, et vérification de types du JavaScript dans les trous — la même expérience qu'en JSX. Référencez-le dans tsconfig.json :

{ "compilerOptions": { "plugins": [{ "name": "@fluixi/ts-plugin" }] } }

Quand choisir html``

  • Vous ne voulez pas de jsx/jsxImportSource dans votre tsconfig (une bibliothèque, un script, du TypeScript simple).
  • Vous préférez le balisage en template littéral.
  • Vous voulez les directives réservées à html `` : if/else, each, bind:, class:name, style:prop.
  • Vous voulez mélanger les deux — un bloc html `` dans un composant JSX, ou l'inverse : les deux compilent.

Ensuite : la référence complète des directives (événements, liaisons, if/each, …).