10 avril 2025

JSX, un standard du développement d’interfaces

JSX est l’acronyme de « JavaScript XML », ce qui reflète la nature de cette syntaxe qui permet d’écrire des structures similaires à du XML, en particulier du HTML et du SVG, directement dans du code JavaScript. Il a été créé lors du développement de React: les équipes de développement de chez Facebook cherchaient une solution pour rendre le développement d’interfaces intuitif et déclaratif.

Bien que n’étant pas un standard officiel, le JSX est un standard de facto dans le développement web moderne, pour preuve de nombreuses bibliothèques et frameworks ont commencé à l’adopter directement ou des variantes proches de cette syntaxe:

  • Preact a utilisé JSX de manière compatible avec React
  • Inferno a implémenté sa propre version optimisée
  • Stencil et Atomico l’utilisent pour créer des composants web
  • Vue.js a introduit un support optionnel de JSX
  • Solid.js utilise sa propre version de JSX, sans Virtual DOM et avec une compilation spécifique
  • HTM (Hyperscript Tagged Markup) ou lit utilisent une syntaxe similaire à JSX, sans transpilation

L’adoption large de JSX présente plusieurs avantages :

  • Transfert de compétences : les développeurs peuvent passer d’un framework à l’autre plus facilement
  • Écosystème d’outils partagé : support IDE, linting, formatage et typage, transpileur
  • Standardisation implicite malgré l’absence de spécification officielle
  • Évolution parallèle avec des innovations partagées entre frameworks

Simple et fonctionnel

JSX est essentiellement une façon d’écrire des éléments d’interface utilisateur, ensuite transformés en JavaScript par un transpileur comme Typescript ou Babel, utilisable sur un serveur ou dans un navigateur. Par exemple, ce code JSX :

<div className="container">
  <h1>Mon titre</h1>
  <p>Mon paragraphe</p>
</div>

Chaque « balise » HTML (ou SVG) est transformée en un appel de fonction, dans le cas de react cela donne:

React.createElement(
  'div',
  { className: 'container' },
  React.createElement('h1', null, 'Mon titre'),
  React.createElement('p', null, 'Mon paragraphe')
);

Mais le réel intérêt de JSX réside dans la possibilité d’utiliser n’importe quelle expression JavaScript directement dans la syntaxe qui décrit l’interface:

<div>
  {isLoggedIn ? (
    <UserProfile data={userData} />
  ) : (
    <LoginForm onSubmit={handleLogin} />
  )}
  
  {items.map(item => (
    <ListItem key={item.id} data={item} />
  ))}
</div>

Cela rend naturel d’exprimer des conditions, des boucles et rend la composition de composants extrêmement facile.

Au final, l’imbrication des appels de fonctions permet, soit de construire un DOM virtuel, soit directement la structure HTML pour le rendu coté serveur, ou DOM dans le cas de Solid.js et des librairies comme Svelte avec jsx et Stencil ou Mitosis deux framework universels qui permet d’écrire des composants en JSX et de les compiler vers différentes plateformes.

La diversité de ces bibliothèques démontrent que JSX est avant tout une syntaxe déclarative qui s’adapte à différents modèles d’exécution, et pas uniquement au fonctionnement du DOM virtuel popularisé par React. Il y a d’ailleurs de nombreux avantages de l’approche sans DOM virtuel:

  1. Performance : Manipulation plus directe du DOM sans couche d’abstraction
  2. Taille des fichiers compilés : Généralement plus léger car moins de code d’exécution
  3. Réactivité granulaire : Mises à jour plus précises et efficaces
  4. Prédictibilité : Comportement du DOM natif

Un implémentation minimale de JSX permettant de créer des éléments DOM permet de se rendre compte de la simplicité du principe:

Implémentation minimale d'un fonction qui permet de créer des éléments DOM à partir de JSX

Support universel et personnalisation

JSX est devenu un langage universel pour décrire des interfaces utilisateur, chaque framework l’adaptant à ses besoins spécifiques tout en maintenant une expérience de développement cohérente. Cette universalité est possible grâce au support robuste offert par les systèmes de build modernes, qui jouent un rôle crucial dans son adoption, y compris des versions alternatives. La configuration d’un JSX personnalisé vous permet d’adapter cette syntaxe à vos besoins spécifiques ou de l’utiliser avec d’autres bibliothèques que React:

Configuration Babel

Babel, outil historique pour compiler du JSX. Dans votre fichier .babelrc ou babel.config.js :

{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "pragma": "h", // Fonction à utiliser au lieu de React.createElement
      "pragmaFrag": "Fragment", // Fonction pour les fragments (optionnel)
      "throwIfNamespace": false // Pour supporter les namespaces XML, svg en particulier
    }]
  ]
}

Options de TypeScript / Deno

Typescript offre un support de la compilation du JSX ainsi que le typage dans les fichiers .tsx, dans votre tsconfig.json

{
  "compilerOptions": {
    "jsx": "react", // mode classique ou "react-jsx", react 17 et +
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment"
  }
}

Note: React 17 élimine le besoin d’importer React dans chaque fichier utilisant JSX, un runtime minimal react/jsx-runtime est utilisé à la place.

Avec ESBuild

Un bundler rapide et facile a configurer, dans votre esbuild.config.ts

esbuild.build({
  jsxFactory: "h",
  jsxFragment: "Fragment"
})

JSX est également disponibles dans les outils les plus récents comme SWC.

Avec une en-tête dans les fichiers

Enfin, si votre outil de compilation support le JSX, vous pouvez aussi spécifier via une notation « pragma » directement dans chaque fichier quelles fonctions doivent être utilisées pour transformer le JSX :

/** @jsx Inferno.createElement */
/** @jsxFrag Inferno.createFragment */
import Inferno from "inferno";

function Hello({ name, message }) {
  return <><div>Hello {name}</div>{message}</>;
}

Comparatif et particularités des implémentations

FrameworkFonction JSXParticularitésModèle de réactivité
ReactReact.createElementVirtual DOM, re-rendus completsBasé sur les re-rendus de composants
PreacthAPI compatible React, plus légerSimilaire à React mais optimisé
Solid.jscreateComponentPas de Virtual DOM, compilation fineRéactivité granulaire, pas de re-rendu
InfernoInferno.createElementOptimisé pour la performanceVirtual DOM avec détection optimisée
Deno (Fresh)h (Preact)Rendu serveur et hydratation partielleBasé sur Preact avec optimisations

Typage et intégration dans les IDE

L’association de JSX avec le systèmes de typage de TypeScript et son intégration dans les IDE améliore l’expérience de développement en offrant une détection des erreurs et une autocomplétion contextuelle des propriétés et attributs. Au cœur de cette intégration se trouve JSX.intrinsics, un mécanisme qui définit les éléments natifs reconnus par le compilateur et permet aux IDE de fournir des validations spécifiques à chaque élément HTML ou composant personnalisé.

namespace JSX {
  interface IntrinsicElements {
    div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
    span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>;
    // ... et d'autres éléments HTML
  }
}

L’un des aspects puissants de JSX.intrinsics est qu’il peut être étendu ou modifié:

declare namespace JSX {
  interface IntrinsicElements {
    // Ajouter un élément personnalisé
    'custom-element': any;
    // Ou redéfinir un élément existant
    'div': { specialAttribute?: string } & React.HTMLAttributes<HTMLDivElement>;
  }
}

Cette fonctionnalité est particulièrement utile dans:

  1. Frameworks personnalisés: Si vous créez votre propre moteur de rendu avec JSX, vous pouvez définir exactement quels éléments sont supportés.
  2. Web Components: Pour intégrer des Web Components natifs dans JSX.
  3. Moteurs de rendu non-DOM: Pour les moteurs de rendu qui ciblent des plateformes comme Canvas, la VR, ou des environnements natifs.
namespace JSX {
  interface IntrinsicElements {
    box: { width?: number; height?: number; color?: string };
    text: { content: string; size?: number };
  }
}

Ainsi, JSX.intrinsics est un mécanisme qui permet l’extensibilité de JSX au-delà du simple HTML et du DOM, le rendant adaptable à pratiquement n’importe quelle interface de programmation déclarative.

Pour conclure

J’espère que cette exploration de JSX permettra de démystifier cette syntaxe si commune mais dont le fonctionnement est parfois ignoré. N’hésitez pas à explorer d’autres frameworks comme Solid.js, Stencil ou Atomico qui réinventent l’utilisation de JSX, souvent avec des performances supérieures et des approches intéressantes.

Dans mon prochain article, je souhaite continuer a rendre abordable des technologies modernes du développement web, je couvrirai les « signaux« , un système de réactivité qui offre une alternative performante et simple d’utilisation au modèle de réactivité de React et constituent le cœur de Solid.js et des nouvelles versions de frameworks établis comme Angular.

En attendant, vous retrouvez d’autres articles sur les technologies Front qui permettent d’améliorer vos applications et développement Frontend, par exemple sur l’internationalisation, y compris dans vos styles et en vous appuyant sur les API natives Intl.