- Tout en conservant le rendu côté serveur basé sur
html/template de Go, HTMX ajoute des interactions dynamiques, avec un seul moteur de rendu capable de renvoyer au choix une page complète ou des fragments HTML
- Le balisage est réparti entre
base.tmpl, des templates de page et des fragments réutilisables, et l’usage de embed.FS de Go 1.16 avec un ensemble de templates partagé simplifie le déploiement et l’architecture du rendu
- Selon
HX-Request, les résultats de recherche sont renvoyés sous forme de page complète ou de lignes de tableau, et Vary: HX-Request ainsi que historyRestoreAsHxRequest: false évitent les erreurs de cache et de restauration via le bouton retour
- Pour rediriger une requête HTMX vers une autre page, il faut utiliser
HX-Redirect avec une réponse 2xx plutôt qu’un 3xx classique, et responseHandling pour 204, 422, 4xx et 5xx permet de contrôler la zone remplacée et l’affichage des erreurs
- Une configuration de départ consiste à désactiver le cache du local storage et l’héritage des attributs, puis à définir un délai maximal pour les requêtes ; à plus grande échelle, on peut ajouter des templates de layout intermédiaires pour prendre en charge des structures d’écran distinctes, comme une zone d’administration
Pourquoi combiner le rendu serveur de Go avec HTMX
- HTMX permet d’ajouter des interactions dynamiques avec peu de JavaScript, tout en conservant la cohérence et la sécurité du rendu HTML côté serveur offertes par
html/template de Go
- L’implémentation s’articule autour de trois axes
- une structure de templates prenant en charge à la fois les pages complètes et les réponses HTML partielles
- la gestion des redirections et des erreurs dans un environnement HTMX
- la configuration qui modifie les valeurs par défaut de HTMX et les raisons de ces choix
- L’exemple part d’une fonction basique qui remplace un bouton par une image, puis s’étend à un écran de recherche filtrant une liste pendant la saisie du nom d’utilisateur ou de l’adresse e-mail
- Le même modèle de templates peut s’appliquer non seulement à HTMX, mais aussi à des outils HTML-over-the-wire comme Unpoly et Hotwire
- Si vous ne maîtrisez pas encore les bases de HTMX, mieux vaut commencer par consulter la documentation officielle
Structure du répertoire du projet et des fichiers statiques
- Le projet est divisé en plusieurs zones selon leur rôle
assets/html/base.tmpl : squelette HTML partagé par toutes les pages
assets/html/pages/ : contenu des pages individuelles
assets/html/partials/ : fragments HTML réutilisés à plusieurs endroits
assets/static/css, assets/static/img, assets/static/js : ressources statiques
cmd/web/ : démarrage du serveur, handlers et code du moteur de rendu HTML
- Le squelette initial est créé avec les commandes suivantes
go mod init example.com/htmx
mkdir -p assets/static/css assets/static/img assets/static/js assets/html/partials assets/html/pages cmd/web
touch assets/efs.go assets/html/base.tmpl assets/html/partials/images.tmpl assets/html/pages/home.tmpl cmd/web/main.go cmd/web/handlers.go cmd/web/html.go
- HTMX peut être installé via un CDN ou NPM, mais l’approche privilégiée ici consiste à télécharger une copie puis à la servir directement comme fichier statique de l’application
- la configuration est plus simple et évite les inconvénients liés à l’usage d’un CDN
htmx.org@2.0.10 et le framework CSS sans classes bamboo.css@1.4.0 sont utilisés
wget -P assets/static/js https://cdn.jsdelivr.net/npm/htmx.org@2.0.10/dist/htmx.min.js
wget -P assets/static/css https://cdn.jsdelivr.net/npm/bamboo.css@1.4.0/dist/bamboo.min.css
Séparer les templates de base, de page et partiels
base.tmpl définit le squelette global du document, notamment <head>, le titre commun et <main>, puis y insère page:title et page:content
- Bamboo CSS et le script HTMX sont chargés dans le template de base, et le script HTMX utilise l’attribut
defer
- le navigateur récupère le script en parallèle pendant l’analyse du HTML
- le script s’exécute une fois l’analyse du HTML et la construction du DOM terminées
- Tous les templates reçoivent un nom explicite via
{{define}}...{{end}}, sans dépendre du nom du fichier
- le code Go peut ainsi référencer les templates de manière cohérente à partir de leur seul nom défini
- le
: de page:title est un simple séparateur arbitraire ; des noms comme page_title, page-title, pageTitle ou title fonctionneraient aussi
- Deux comportements sont définis sur le bouton de la page d’accueil
hx-get="/gopher" : envoie une requête GET /gopher au clic
hx-swap="outerHTML" : remplace le bouton lui-même par le HTML de la réponse
partial:image:gopher est un fragment d’image réutilisable, et width="{{.}}" lui transmet la largeur de l’image au moment de l’appel
- Les fragments HTML utilisés uniquement dans une page donnée peuvent rester dans le fichier de cette page plutôt que dans le répertoire partagé
partials
- l’idée est de placer dans le répertoire commun les fragments utilisés par plusieurs pages, et de garder les fragments propres à une page à côté du contenu concerné
Inclure le HTML et les ressources statiques dans le binaire Go
- Depuis l’ajout de l’embarquement de fichiers dans Go 1.16, il est possible d’inclure le HTML et les ressources statiques dans le binaire Go plutôt que de les lire sur le disque à l’exécution
- La directive
//go:embed "html" "static" place ces deux répertoires dans embed.FS, puis fs.Sub() les sépare en deux sous-systèmes de fichiers
HTMLFiles : système de fichiers des templates avec html comme racine
StaticFiles : système de fichiers des ressources statiques avec static comme racine
- Cette séparation apporte deux avantages
- le code HTML et le code des fichiers statiques n’accèdent pas inutilement aux fichiers de l’autre domaine
- il n’est pas nécessaire de préfixer les chemins par
html/ ou static/ lors de l’ouverture des fichiers
- Si l’on préfère éviter
panic() dans sub(), on peut retourner une erreur et initialiser les deux variables dans main()
- Dans l’implémentation actuelle, pour que
fs.Sub() échoue, il faudrait que la valeur du répertoire ne soit pas un chemin valide ; comme les chaînes fixes "html" et "static" passent ce contrôle, le risque de panic à l’exécution est très faible
Ensemble de templates partagé et htmlRenderer
htmlRenderer gère le flux de rendu de la manière suivante
- il analyse une seule fois au démarrage un ensemble de templates partagé
- il clone cet ensemble partagé à chaque rendu
- il analyse ensuite en plus les templates de page nécessaires à la requête
- il exécute le template désigné et l’envoie dans la réponse HTTP
newHTMLRenderer() enregistre d’abord un template.FuncMap, puis analyse les templates communs
now est relié à time.Now
- d’autres fonctions de template personnalisées peuvent être ajoutées au même endroit
render() crée un ensemble de templates par requête avec sharedTemplates.Clone(), puis l’étend avec ParseFS() si additionalTemplateFiles est fourni
- Les templates sont d’abord exécutés dans un
bytes.Buffer, puis le code de statut est écrit avant l’envoi du corps de réponse
- Lors de l’initialisation du serveur,
"base.tmpl" et "partials/*.tmpl" sont analysés dans l’ensemble partagé
- le template de base et tous les fragments communs sont ainsi toujours disponibles dans le moteur de rendu
- seuls les templates spécifiques aux pages sont ajoutés au moment de l’appel du handler
- Les fichiers statiques sont servis via
http.FileServerFS(assets.StaticFiles) sur le chemin /static/, et le serveur écoute sur :5051
Rendu de pages complètes et de fragments HTML
- Le handler de la page d’accueil renvoie le document complet avec l’appel suivant
app.html.render(w, 200, nil, "base", "pages/home.tmpl")
- Cet appel ajoute
pages/home.tmpl à l’ensemble partagé, puis exécute le template base avec un 200 OK
- Le handler
/gopher n’analyse aucun fichier supplémentaire et exécute uniquement partial:image:gopher, déjà inclus dans l’ensemble partagé
width := 100
app.html.render(w, http.StatusOK, width, "partial:image:gopher")
- HTMX remplace le bouton par l’image reçue depuis
/gopher, conformément au paramètre hx-swap="outerHTML"
- Cette structure offre les avantages suivants
- Les templates et les ressources statiques sont inclus dans le binaire, ce qui facilite le déploiement
- Le même
render() peut renvoyer à la fois une page HTML complète et un fragment HTML spécifique
- Les templates de base et partiels réduisent la duplication du balisage
- Les templates partiels peuvent être réutilisés dans le template de base, le contenu des pages et d’autres templates partiels
Recherche d’utilisateurs avec mise à jour à la saisie
- La fonctionnalité de recherche est divisée en deux routes
GET /users : une page HTML complète contenant les informations de tous les utilisateurs
GET /users/search : un fragment HTML qui ne renvoie que les lignes du tableau des utilisateurs dont le nom ou l’e-mail correspond à la requête
- Le champ de recherche combine les attributs HTMX suivants
hx-get="/users/search" envoie la requête de recherche
hx-trigger="input changed delay:500ms, keyup[key=='Enter']" déclenche la requête 500 ms après une modification de saisie ou lorsqu’on appuie sur Entrée
- Le terme recherché est transmis sous forme de chaîne de requête, par exemple
GET /users/search?query=foo
hx-target="#search-results" insère la réponse dans <tbody id="search-results">
hx-push-url="true" met à jour la barre d’adresse à chaque requête et ajoute une entrée à l’historique du navigateur
users:rows est un template séparé qui ne rend que les lignes des utilisateurs
- Chaque ligne affiche le nom et l’e-mail
- Si
IsGopher est vrai, partial:image:gopher est rendu avec une largeur de 24
listUsers et searchUsers ajoutent tous deux pages/users.tmpl à l’ensemble partagé, mais n’exécutent pas le même template
listUsers : exécute base et renvoie la page complète
searchUsers : exécute users:rows et ne renvoie que les lignes correspondantes
- Si la requête de recherche est vide, la liste complète des utilisateurs est utilisée ; si une valeur est fournie,
strings.Contains() vérifie le nom et l’e-mail
- Dans une application réelle, on peut envisager les améliorations suivantes
- Fusionner les handlers de liste et de recherche en un seul
- Centraliser les helpers de gestion d’erreurs et de logging
- Ajouter via un middleware les en-têtes Content Security Policy et la récupération après panic
- Définir correctement les délais d’expiration côté serveur
Distinguer page complète et réponse partielle avec HX-Request
- Si un utilisateur ouvre directement
/users/search?query=leo dans son navigateur ou visite un lien partagé, il risque de ne voir que le HTML partiel correspondant aux lignes du tableau
- Les requêtes HTMX incluent toujours l’en-tête
HX-Request: true, qui sert donc à déterminer le type de réponse
func isHTMXRequest(r *http.Request) bool {
return r.Header.Get("HX-Request") == "true"
}
- Le template exécuté par défaut du handler de recherche est
base, puis il ne passe à users:rows que pour les requêtes HTMX
- En accès direct, on reçoit la page complète avec les résultats de recherche
- Pour une requête HTMX, seule la portion de lignes à insérer dans
<tbody> est renvoyée
En-tête Vary et restauration avec le bouton Retour
- Comme une même URL peut renvoyer des réponses différentes selon la valeur de
HX-Request, il faut aussi signaler cette différence au cache
- Ajouter
Vary: HX-Request à toutes les réponses rendues
- Le définir uniquement dans les handlers qui en ont besoin est, à strictement parler, un peu moins coûteux
- Le définir globalement dans le renderer évite les bugs potentiels dus à un oubli
- Lorsque
hx-push-url ou hx-boost ajoute des entrées à l’historique du navigateur, HTMX met en cache le HTML complet de la page dans le stockage local du navigateur
- Le cache par défaut est limité à 10 pages
- Si l’on revient en arrière jusqu’à un point absent du cache, HTMX redemande l’URL au serveur
- Par défaut, les requêtes de restauration incluent
HX-Request: true, ce qui peut conduire le serveur à renvoyer du HTML partiel au lieu d’une page complète
- Si le type de réponse est choisi à partir de
HX-Request, il faut définir historyRestoreAsHxRequest: false
- Cela retire
HX-Request: true des requêtes de restauration après un cache miss
- Le serveur renverra alors une page HTML complète pour cette requête
- La configuration HTMX est placée dans la balise meta
htmx-config de base.tmpl
Rediriger une requête HTMX vers une autre page
- HTMX peut remplacer immédiatement un fragment HTML, comme un message de succès, ce qui réduit le besoin du flux classique Post/Redirect/Get
- Quand il faut envoyer l’utilisateur vers une page complètement différente, comme vers son profil après une connexion réussie, une simple réponse 3xx ne suffit pas
- Le navigateur suit la réponse 3xx avant HTMX
- HTMX ne voit que la réponse finale après redirection et remplace le contenu de la cible existante avec ce contenu
- Pour une requête HTMX, il faut utiliser une réponse 2xx avec l’en-tête
HX-Redirect
HX-Redirect: /foo/bar fait naviguer le navigateur vers cette URL et recharge la page complète
- La requête vers la destination n’inclut pas
HX-Request: true
- Si l’on veut aussi prendre en charge les environnements où JavaScript est désactivé ou où HTMX n’est pas chargé, il faut conserver une réponse 3xx classique pour les requêtes non HTMX
func redirect(w http.ResponseWriter, r *http.Request, url string, code int) {
if isHTMXRequest(r) {
w.Header().Set("HX-Redirect", url)
w.WriteHeader(http.StatusNoContent)
return
}
http.Redirect(w, r, url, code)
}
- Pour aller vers
/profile après la connexion, on peut l’appeler ainsi
redirect(w, r, "/profile", http.StatusSeeOther)
Différence entre HX-Redirect et HX-Location
HX-Location réalise un comportement proche d’une redirection sans recharger toute la page
- Il récupère le HTML de l’URL cible
- Il remplace la réponse dans le
<body>
- Il ajoute une nouvelle entrée à l’historique du navigateur
- Lors de la requête vers l’URL cible,
HX-Request: true est inclus
- Cela peut poser problème si la route cible renvoie du HTML partiel selon
HX-Request
- Le handler ne peut pas distinguer une requête issue de
HX-Location d’une requête HTMX classique
- La première nécessite une page complète, la seconde une réponse partielle
- Contrairement à la restauration d’historique, il n’existe pas de réglage permettant de désactiver
HX-Request: true pour une navigation HX-Location
- Dans la plupart des cas, il est plus simple et plus sûr d’utiliser
HX-Redirect, quitte à recharger la page complète
- Si la route cible est configurée pour toujours renvoyer du HTML complet,
HX-Location peut aussi convenir
Gestion des réponses 204·422·4xx·5xx
- Par défaut, HTMX ne remplace pas le DOM pour les réponses 4xx ou 5xx
- L’écran actuel reste tel quel
- L’erreur est consignée dans la console des outils de développement
- Même si le serveur renvoie
500 Internal Server Error à cause d’un nom de template inexistant, l’utilisateur ne voit généralement pas l’erreur à l’écran avec la configuration par défaut
- Pour afficher les messages d’erreur à l’utilisateur, les réponses 4xx et 5xx sont configurées pour remplacer l’ensemble du
<body>
- L’exception est
422 Unprocessable Content
- Il est utilisé pour renvoyer un formulaire contenant des erreurs de validation
- La réponse doit remplacer normalement la cible HTMX existante
responseHandling est configuré ainsi selon les codes d’état
204 No Content : ne modifie pas le DOM
422 Unprocessable Content : remplace normalement la cible existante
- Les autres 4xx et 5xx : remplacent la réponse dans
<body>
- Les autres réponses : remplacent normalement la cible existante
"responseHandling":[
{"code":"204", "swap": false},
{"code":"422", "swap": true},
{"code":"[45]..", "swap": true, "target": "body"},
{"code":"...", "swap": true}
]
- Si, dans une interaction donnée, les erreurs doivent être insérées dans une cible autre que
<body>, il est possible de remplacer le comportement par défaut avec l’extension response target
Vérifier l’URL actuelle dans la barre d’adresse du navigateur
- HTMX ne modifie pas l’URL du navigateur pendant une requête AJAX et le remplacement de la réponse si
hx-boost, hx-push-url et hx-replace-url ne sont pas utilisés
- Pour cette raison,
r.URL dans le gestionnaire Go peut être différente de l’URL que l’utilisateur voit dans la barre d’adresse
- HTMX transmet l’URL actuellement affichée par le navigateur dans l’en-tête
HX-Current-URL de chaque requête
- La fonction utilitaire analyse cet en-tête en
url.URL et renvoie r.URL si l’en-tête est absent
func browserURL(r *http.Request) (*url.URL, error) {
cu := r.Header.Get("HX-Current-URL")
if cu != "" {
return url.Parse(cu)
}
return r.URL, nil
}
Configuration qui modifie les valeurs par défaut de HTMX
- Avec
historyCacheSize: 0, le cache de pages en local storage de HTMX est entièrement désactivé
- Le cache en local storage peut être une source de bugs et de problèmes de sécurité
- Quand on clique sur retour, le HTML est rechargé depuis le serveur au lieu d’être pris depuis le cache
- Dans les futures versions de HTMX, le cache en local storage devrait aussi être désactivé par défaut pour la même raison
- Avec
disableInheritance: true, l’héritage des attributs HTMX est désactivé
- Il est plus clair de toujours déclarer les attributs explicitement
- Cela réduit le risque de bugs ou de comportements non intentionnels
- Dans les futures versions de HTMX, l’héritage des attributs devrait lui aussi être désactivé par défaut
- Avec
includeIndicatorStyles: false, HTMX n’injecte pas les styles d’indicateur
- Les styles d’indicateur sont définis manuellement avec les autres règles CSS pour garder une présentation cohérente
- Par défaut, les requêtes HTMX n’ont pas de délai d’expiration et attendent jusqu’à ce que le serveur réponde
- Selon la manière dont l’application Go gère les timeouts et les deadlines, vous pouvez définir
timeout en millisecondes
- Le point de départ proposé est
timeout: 5000
- La configuration par défaut inclut aussi
historyRestoreAsHxRequest: false ainsi qu’un responseHandling selon les codes d’état
<meta
name="htmx-config"
content='{
"includeIndicatorStyles": false,
"historyCacheSize": 0,
"historyRestoreAsHxRequest": false,
"responseHandling":[
{"code":"204", "swap": false},
{"code":"422", "swap": true},
{"code":"[45]..", "swap": true, "target": "body"},
{"code":"...", "swap": true}
],
"timeout": 5000
}'
>
Mise en page par page pour les grandes applications
- Si le template de base et les templates propres à chaque page ne suffisent pas, il est possible d’ajouter un template de layout entre ces deux niveaux
- C’est adapté à des sections comme une page d’administration, qui nécessitent un titre, un menu de navigation et une structure de contenu différents des pages ordinaires
base appelle layout au lieu d’appeler directement page:content
<body>
{{template "layout" .}}
</body>
layouts/admin.tmpl définit la structure commune de la zone d’administration et appelle page:content à l’intérieur
- Titre de la zone d’administration
- Menu de navigation vers les utilisateurs et les commandes
<main> destiné à accueillir le contenu propre à chaque page
- Le gestionnaire transmet à
render() à la fois le layout à utiliser et le template de page
app.html.render(
w,
200,
nil,
"base",
"layouts/admin.tmpl",
"pages/admin-orders.tmpl",
)
- Cela permet de combiner un template de base partagé avec des layouts par parcours et du contenu de page, et d’étendre le même modèle de rendu à des écrans qui nécessitent une structure distincte, comme la zone d’administration
1 commentaires
Avis sur Hacker News
Aime Go + HTMX, et l’utilise avec a-h/templ pour renforcer la sûreté de typage des templates, composants et fragments HTML A publié cet ensemble d’outils, composé de Go, Unix et SQLite, sous le nom de stack GUS : https://housecat.com/blog/the-gus-stack-go-unix-sqlite Il s’inspire fortement de la stack GUTS d’exe.dev, mais utilise HTMX au lieu de TypeScript : https://exe.dev/docs/guts Utilise cockroachdb/errors pour les erreurs avec stack trace, templ pour le HTML type-safe, fuego pour générer les spécifications OpenAPI, sqlc pour générer le code SQL, modernc.org/sqlite pour SQLite en pur Go, goose pour les migrations, dbos pour les workflows de persistance, et rod pour les tests et l’automatisation Chrome/CDP Cette combinaison est productive aussi bien pour coder à la main ou avec l’aide d’un agent que pour construire et déployer un binaire unique
go generate ./...comme étape de build apporte beaucoup, et goverter sert aussi à générer les conversions entre les modèles sqlc, les objets de templates et les valeurs de retour Environ 50 % du boilerplate est généré automatiquement, ce qui donne une très bonne sûreté de typageHTMX est excellent pour de nombreux usages, mais il est très difficile à introduire si les collègues de l’équipe ne sont pas d’accord Les réactions disant que ce n’est pas une technologie sérieuse persistent, toutes sortes de bugs sont d’abord attribués à HTMX, et même lorsqu’il s’avère plus tard que c’était une méprise, la confiance est déjà entamée C’est arrivé même dans la meilleure équipe avec laquelle la personne ait travaillé jusqu’ici, et la leçon retenue est qu’il faut choisir ses combats Considère aussi que html/template de Go a une interface particulièrement peu naturelle. Comme le recommande 《A Philosophy of Software Design》, il faudrait repousser la complexité vers l’intérieur ; or une structure qui oblige à se soucier de cloner les templates à chaque rendu HTML est inutilement complexe
base.tmplenBASE_BEGINetBASE_END, puis de les utiliser dans le template final Le titre propre à chaque page doit être transmis à l’exécution, mais la méthode de l’article d’origine finira de toute façon par casser dès que le support multilingue commenceraA pris beaucoup de plaisir à utiliser HTMX dans un projet récent Pour quelqu’un qui connaît le Web d’avant AngularJS et React, le fait de pouvoir construire de vraies pages et de réduire au minimum la quantité de JavaScript est particulièrement appréciable C’est possible en JavaScript pur aussi, mais HTMX évite le boilerplate répétitif de gestion des événements Si la philosophie du frontend moderne ne vous plaît pas, cela vaut la peine d’essayer ; les premiers exemples du site officiel sont très basiques, mais avec un peu plus d’apprentissage, l’outil devient bien plus puissant
J’ai essayé de créer un projet assez conséquent avec HTMX + Go, mais ce n’était pas encore suffisant, et je ne suis pas sûr que cela atteigne un jour le niveau que je souhaite. C’est excellent pour les applis CRUD simples et les tableaux de bord d’administration, mais dès qu’il y a beaucoup de composants interconnectés, d’état partagé et d’interactions complexes, cela devient vite difficile à gérer. J’avais choisi HTMX parce que je n’aime pas React, mais j’ai finalement gardé Go pour le backend et basculé le frontend en mode SPA avec SvelteKit. En séparant proprement les deux, j’ai pu développer et maintenir des interfaces complexes beaucoup plus facilement. Svelte ressemble davantage à une extension naturelle du HTML qu’à un autre langage comme JSX, son modèle de gestion d’état et de composants est simple, et la nouvelle syntaxe
$stateest particulièrement agréable.Ces temps-ci, j’écris presque tous mes side projects en Go + HTMX, avec ou sans l’aide des LLM. Opus et GPT maîtrisent aussi très bien cette combinaison ; on peut construire et démarrer rapidement, et comme c’est un binaire unique, le déploiement et l’hébergement sont pratiques. C’est une très bonne stack pour itérer rapidement.
Si vous utilisez HTMX, je recommande vivement une approche de génération HTML qui permette, côté backend aussi, d’extraire facilement des fragments HTML communs sous forme de fonctions, comme des composants à la React. Avec HTMX, selon le contexte, il faut de la flexibilité dans le HTML généré par le backend, par exemple ajouter ou retirer des balises juste à côté d’un HTML donné. Par exemple, s’il trouve une balise `` au niveau supérieur, il met à jour le titre de la page. Avec les moteurs de templates classiques basés sur des chaînes, ce genre de chose est difficile, mais avec une bibliothèque HTML intégrée au langage, c’est simple, et l’on peut utiliser toutes les capacités d’abstraction du langage, ce qui se marie particulièrement bien avec HTMX. Pour un backend JavaScript, on peut utiliser une fonction de tagged template literal comme https://github.com/WebReflection/uhtml-ssr ; en Go, https://www.gomponents.com/ ; en Scala, ScalaTags.
renderToStaticMarkup, mais il vaut sans doute mieux choisir une bibliothèque JSX écrite spécifiquement pour le backend.J’ai découvert Go avec le livre d’Alex Edwards et Learn Go with Tests, et je les recommande encore aujourd’hui. Cela me donne envie d’essayer de convertir certaines parties de vieux projets à HTMX.
J’ai créé pour mon usage personnel un framework basé sur Kotlin + HTMX : https://github.com/reubenfirmin/zoned L’objectif était de vérifier s’il était possible de rendre toute une application web typée de bout en bout, et j’utilise Kotlinx.html pour écrire le HTML avec un DSL proche de JSX. J’ai fait moi-même les 90 premiers pour cent, puis j’ai demandé l’aide de Claude pour finaliser les 10 derniers pour cent et le README en vue de la publication.
J’aime Go comme Alex Edwards, mais mes résultats avec HTMX ont toujours été décevants. J’ai l’impression que la complexité du codebase augmente deux fois plus vite que celle de l’application elle-même, et je finis toujours par tomber sur des cas limites dont on ne sort pas sans bricolages étranges. Je comprends pourquoi on peut détester les paquets Node, mais HTMX me semble être une surcompensation. Le temps gagné à ne pas se battre avec JSON est plus que triplé par le temps nécessaire pour obtenir une appli qui ait vraiment bonne apparence et un bon ressenti. Un template Mantine se configure en deux minutes et donne accès à d’excellents composants d’UI ; en incluant les assets statiques générés, on peut tout aussi bien en faire un binaire Go unique : https://github.com/mantinedev/vite-min-template
Je préfère largement Go + Datastar, parce que c’est plus simple.