WebAssembly + Go + React : Valider tes formulaires comme un chef !


Salut toi ! Alors comme ça, tu en as marre que la validation de tes formulaires JavaScript qui rame ? Tu aimerais bien avoir les performances du natif sans te prendre la tête avec du C++ ? J'ai la solution parfaite pour toi : WebAssembly avec Go !
Aujourd'hui, on va découvrir GoWM, un petit bijou qui va révolutionner ta façon d'intégrer WebAssembly dans tes apps React, Vue.js ou dans du Node.js. Prépare ton café, on va s'amuser !
GoWM, qu'est-ce que c'est que ce truc ?
GoWM (Go Wasm Manager), c'est un peu comme ton couteau suisse pour WebAssembly. Cette bibliothèque te simplifie la vie au maximum pour charger et utiliser des modules WASM Go dans tes apps JavaScript.
Fini les galères d'intégration ! GoWM te donne :
Une interface unifiée pour charger tes modules WASM Go (enfin !)
Un support complet pour Node.js et navigateur avec détection automatique (malin, non ?)
Des hooks React intégrés (
useWasm
,useWasmFromNPM
) parce qu'on aime React iciDes composables Vue.js pour les fans de Vue 3
Un chargement depuis NPM avec résolution automatique (plus besoin de se creuser la tête)
Une gestion robuste des erreurs et détection de fonctions
Une gestion automatique de la mémoire (tu peux dormir tranquille)
Pourquoi WebAssembly pour valider tes formulaires ?
Alors là, excellente question ! Tu sais, quand tu as des formulaires avec des règles métier complexes, ça peut vite devenir l'enfer côté performance. Genre, tu as des regex pourries, des validations cross-field, et ton navigateur qui commence à suffoquer.
WebAssembly, c'est comme avoir une Ferrari dans ton navigateur. Du code Go compilé qui tourne à des vitesses de malade, directement dans le browser. C'est beau, non ?
Allez, on installe tout ça !
Bon, assez parlé, on passe aux choses sérieuses :
npm install gowm
# ou si tu es team Yarn
yarn add gowm
Facile, hein ? 😄
Création de notre module WASM de validation
Structure du projet
D'abord, organisons-nous correctement :
validator-wasm/
├── main.go # Notre code Go de la mort
├── go.mod # Configuration Go
├── build.sh # Script magique de compilation
├── package.json # Config NPM
└── README.md # Documentation (sois pas fainéant !)
Le code Go du Wasm
Allez, on s'attaque au vif du sujet. Voici notre validateur Go qui sera builder en wasm :
//go:build js && wasm
package main
import (
"encoding/json"
"fmt"
"regexp"
"strings"
"syscall/js"
)
type ValidationRule struct {
Field string `json:"field"`
Required bool `json:"required"`
MinLen int `json:"minLen"`
MaxLen int `json:"maxLen"`
Pattern string `json:"pattern"`
Type string `json:"type"`
}
type ValidationResult struct {
Valid bool `json:"valid"`
Errors map[string]string `json:"errors"`
}
// validateEmail - parce qu'on en a tous marre des emails pourris
func validateEmail(email string) bool {
pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
matched, _ := regexp.MatchString(pattern, email)
return matched
}
// validatePhone - pour les numéros français
func validatePhone(phone string) bool {
pattern := `^(?:\+33|0)[1-9](?:[0-9]{8})$`
matched, _ := regexp.MatchString(pattern, phone)
return matched
}
// validateForm - la fonction qui fait tout le boulot
func validateForm(this js.Value, args []js.Value) interface{} {
if len(args) != 2 {
return js.ValueOf(map[string]interface{}{
"valid": false,
"errors": map[string]string{"system": "Arguments invalides, mon reuf !"},
})
}
// Parse des données (attention, ça peut piquer)
formDataJSON := args[0].String()
rulesJSON := args[1].String()
var formData map[string]interface{}
var rules []ValidationRule
if err := json.Unmarshal([]byte(formDataJSON), &formData); err != nil {
return js.ValueOf(map[string]interface{}{
"valid": false,
"errors": map[string]string{"system": "Données formulaire pourries"},
})
}
if err := json.Unmarshal([]byte(rulesJSON), &rules); err != nil {
return js.ValueOf(map[string]interface{}{
"valid": false,
"errors": map[string]string{"system": "Règles de validation foireuses"},
})
}
result := ValidationResult{
Valid: true,
Errors: make(map[string]string),
}
// La magie opère ici
for _, rule := range rules {
value, exists := formData[rule.Field]
valueStr := ""
if exists && value != nil {
valueStr = strings.TrimSpace(value.(string))
}
// Champ requis ? On vérifie !
if rule.Required && valueStr == "" {
result.Valid = false
result.Errors[rule.Field] = "Ce champ est requis, fais un effort !"
continue
}
if valueStr == "" {
continue // On skip si pas requis et vide
}
// Longueur minimale
if rule.MinLen > 0 && len(valueStr) < rule.MinLen {
result.Valid = false
result.Errors[rule.Field] = fmt.Sprintf("Minimum %d caractères requis", rule.MinLen)
continue
}
// Longueur maximale
if rule.MaxLen > 0 && len(valueStr) > rule.MaxLen {
result.Valid = false
result.Errors[rule.Field] = fmt.Sprintf("Maximum %d caractères autorisés", rule.MaxLen)
continue
}
// Validation par type
switch rule.Type {
case "email":
if !validateEmail(valueStr) {
result.Valid = false
result.Errors[rule.Field] = "Merci d'entrer une adresse email valide"
}
case "phone":
if !validatePhone(valueStr) {
result.Valid = false
result.Errors[rule.Field] = "Ce numéro de téléphone, c'est n'importe quoi"
}
}
// Pattern personnalisé pour les perfectionnistes
if rule.Pattern != "" {
matched, err := regexp.MatchString(rule.Pattern, valueStr)
if err != nil || !matched {
result.Valid = false
result.Errors[rule.Field] = "Format invalide, recommence !"
}
}
}
// On retourne le tout en JSON
resultJSON, _ := json.Marshal(result)
return js.ValueOf(string(resultJSON))
}
func main() {
// On expose notre fonction au monde JavaScript
js.Global().Set("validateForm", js.FuncOf(validateForm))
// Signal de prêt pour GoWM
js.Global().Set("__gowm_ready", js.ValueOf(true))
// On maintient le programme en vie (important !)
select {}
}
Script de compilation en Wasm
Créons notre script build.sh
qui va compiler tout ça proprement :
#!/bin/bash
set -e
echo "🔨 Compilation du module de validation WASM..."
# Compilation optimisée (on veut du perf !)
GOOS=js GOARCH=wasm go build \
-ldflags="-s -w" \
-o validator.wasm \
main.go
echo "📦 Copie du runtime Go..."
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" .
echo "✅ Build terminé !"
echo "📊 Taille du fichier WASM: $(du -h validator.wasm | cut -f1)"
Intégration React avec GoWM
Maintenant, la partie vraiment fun : intégrer tout ça dans React ! Voici un composant :
import React, { useState, useEffect } from 'react';
import { load } from 'gowm';
const ContactForm = () => {
const [wasm, setWasm] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const loadWasm = async () => {
try {
setLoading(true);
setError(null);
const wasmModule = await load('/validator.wasm');
setWasm(wasmModule);
setLoading(false);
} catch (err) {
setError(err);
setLoading(false);
}
};
loadWasm();
}, []);
const [formData, setFormData] = useState({
name: '',
email: '',
phone: '',
message: ''
});
const [validationErrors, setValidationErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
// Nos règles de validation (modulables à souhait)
const validationRules = [
{ field: 'name', required: true, minLen: 2, maxLen: 50 },
{ field: 'email', required: true, type: 'email' },
{ field: 'phone', required: false, type: 'phone' },
{ field: 'message', required: true, minLen: 10, maxLen: 500 }
];
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
// Validation en temps réel (parce qu'on est des pros)
if (wasm) {
validateField(name, value);
}
};
const validateField = async (fieldName, value) => {
if (!wasm) {
// Validation de fallback en JavaScript si WASM pas encore chargé
const errors = {};
const rules = validationRules.filter(rule => rule.field === fieldName);
for (const rule of rules) {
if (rule.required && !value.trim()) {
errors[fieldName] = "Ce champ est requis, fais un effort !";
break;
}
if (value.trim() && rule.minLen && value.length < rule.minLen) {
errors[fieldName] = `Minimum ${rule.minLen} caractères requis`;
break;
}
if (value.trim() && rule.type === 'email') {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(value)) {
errors[fieldName] = "Cet email sent le poisson pourri";
break;
}
}
}
setValidationErrors(prev => ({
...prev,
[fieldName]: errors[fieldName] || null
}));
return;
}
try {
const singleFieldData = { [fieldName]: value };
const fieldRules = validationRules.filter(rule => rule.field === fieldName);
// Avec gowm, les fonctions sont disponibles globalement après le chargement
const result = window.validateForm(
JSON.stringify(singleFieldData),
JSON.stringify(fieldRules)
);
const validation = JSON.parse(result);
setValidationErrors(prev => ({
...prev,
[fieldName]: validation.errors[fieldName] || null
}));
} catch (err) {
console.error('Oups, erreur de validation:', err);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!wasm) {
alert('Patience, le validateur charge encore !');
return;
}
setIsSubmitting(true);
try {
// Validation complète (le moment de vérité)
const result = window.validateForm(
JSON.stringify(formData),
JSON.stringify(validationRules)
);
const validation = JSON.parse(result);
if (validation.valid) {
alert('Formulaire nickel ! Envoi en cours...');
// Ici tu peux envoyer tes données
console.log('Données prêtes à partir:', formData);
} else {
setValidationErrors(validation.errors);
}
} catch (err) {
console.error('Erreur lors de la validation:', err);
alert('Oups, quelque chose a foiré !');
} finally {
setIsSubmitting(false);
}
};
if (loading) {
return (
<div className="flex items-center justify-center p-8">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
<p className="mt-2 text-gray-600">Chargement du validateur...</p>
</div>
</div>
);
}
if (error) {
return (
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<p className="text-red-600">Erreur de chargement: {error.message}</p>
</div>
);
}
return (
<div className="max-w-md mx-auto bg-white rounded-lg shadow-md p-6">
<h2 className="text-2xl font-bold mb-6 text-gray-800">Contact</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Nom *
</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleInputChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
validationErrors.name ? 'border-red-500' : 'border-gray-300'
}`}
/>
{validationErrors.name && (
<p className="text-red-500 text-sm mt-1">{validationErrors.name}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Email *
</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleInputChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
validationErrors.email ? 'border-red-500' : 'border-gray-300'
}`}
/>
{validationErrors.email && (
<p className="text-red-500 text-sm mt-1">{validationErrors.email}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Téléphone
</label>
<input
type="tel"
name="phone"
value={formData.phone}
onChange={handleInputChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
validationErrors.phone ? 'border-red-500' : 'border-gray-300'
}`}
/>
{validationErrors.phone && (
<p className="text-red-500 text-sm mt-1">{validationErrors.phone}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Message *
</label>
<textarea
name="message"
rows={4}
value={formData.message}
onChange={handleInputChange}
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
validationErrors.message ? 'border-red-500' : 'border-gray-300'
}`}
/>
{validationErrors.message && (
<p className="text-red-500 text-sm mt-1">{validationErrors.message}</p>
)}
</div>
<button
type="submit"
disabled={isSubmitting}
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? 'Validation en cours...' : 'Envoyer'}
</button>
</form>
</div>
);
};
export default ContactForm;
Pourquoi cette approche ?
Performances.
Avec WebAssembly, ton code de validation tourne à la vitesse de l'éclair. Les regex complexes et la logique métier sont traitées de manière ultra-efficace. Fini les freeze d'interface !
Validation en temps réel sans lag
Grâce au hook useWasm
de GoWM, tu peux valider tes champs en temps réel sans que ton interface rame.
Réutilisabilité au max
Ton module WASM, tu peux :
Le publier sur NPM pour le réutiliser partout
L'utiliser côté serveur avec Node.js (pratique !)
L'intégrer dans des apps Vue.js avec les composables GoWM
Sécurité renforcée
La validation côté client en WASM ajoute une couche de sécurité. Bien sûr, tu gardes ta validation côté serveur (on n'est pas fous !).
Configuration avancée avec GoWM
GoWM, c'est pas que basique. Tu peux faire d’autres trucs sympa :
// Chargement simple
import { load } from 'gowm';
async function loadValidator() {
try {
const validator = await load('/validator.wasm');
// Utilisation directe des fonctions exposées globalement
const result = window.validateForm(dataJSON, rulesJSON);
return JSON.parse(result);
} catch (error) {
console.error('Oops, chargement foiré:', error);
}
}
// Gestion de plusieurs modules (pour les gourmands)
import { load, get, listModules } from 'gowm';
const loadMultipleModules = async () => {
await load('/validator.wasm', { name: 'validator' });
await load('/formatter.wasm', { name: 'formatter' });
// Lister les modules chargés
console.log('Modules chargés:', listModules());
// Récupérer un module spécifique
const validator = get('validator');
};
Support TypeScript (pour les perfectionnistes)
GoWM inclut des types TypeScript complets :
import { load, LoadOptions } from 'gowm';
interface ValidationResult {
valid: boolean;
errors: Record<string, string>;
}
const loadValidator = async (): Promise<void> => {
await load('./validator.wasm');
// Les fonctions WASM sont disponibles globalement
const result: string = (window as any).validateForm(formDataJSON, rulesJSON);
const validation: ValidationResult = JSON.parse(result);
};
Alors, convaincu ? GoWM rend WebAssembly accessible à tous les développeurs JavaScript, même les plus fainéants ! Cette approche te permet de :
Booster les performances de tes validations complexes
Simplifier l'intégration grâce aux hooks React tout prêts
Maintenir un code propre et testable
Réutiliser ta logique entre client et serveur
Que ce soit pour la validation de formulaires, le traitement d'images, ou des calculs de la mort, WebAssembly avec GoWM ouvre de nouvelles possibilités pour tes applications web
Le code source complet de cet exemple sera bientôt disponible sur GitHub.
A plus ! 🚀
Subscribe to my newsletter
Read articles from Ben ✨ directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Ben ✨
Ben ✨
Développeur web français, passionné d'innovation digitale. Je crée des applis innovantes et partage mes astuces sur les solutions opensource.