Composants Vue personnalisés dans une UI générée, en toute sécurité lors de la régénération
Découvrez comment ajouter des composants Vue personnalisés dans une UI générée sans compromettre la régénération, en appliquant des modèles d'isolation, des frontières claires et des règles simples.

Ce qui casse quand vous modifiez une UI générée
Les UIs générées sont faites pour être reconstruites. Sur des plateformes comme AppMaster, le code d'une application web Vue3 est produit depuis des éditeurs visuels. La régénération permet de garder la cohérence entre écrans, logique et modèles de données.
Le problème est simple : si vous modifiez directement des fichiers générés, la prochaine régénération peut écraser vos changements.
C'est pour cela que les équipes ajoutent du code personnalisé. Les blocs UI intégrés couvrent souvent les formulaires et tableaux standards, mais les vraies applications ont parfois besoin d'éléments spéciaux : graphiques complexes, sélecteurs sur carte, éditeurs de texte enrichi, pads de signature ou planificateurs drag-and-drop. Ce sont de bonnes raisons d'ajouter des composants Vue personnalisés, à condition de les traiter comme des extensions, pas comme des modifications directes.
Quand le code personnalisé se mélange au code généré, les problèmes peuvent apparaître plus tard et de façon déroutante. Vous ne le remarquez parfois que lors du prochain changement d'UI qui force une régénération, ou quand un coéquipier ajuste un écran dans l'éditeur visuel. Les problèmes courants incluent :
- Votre balisage personnalisé disparaît parce que le template a été régénéré.
- Les imports ou l'enregistrement cassent parce que des noms de fichiers ou la structure ont changé.
- Une « petite correction » devient un conflit de fusion à chaque déploiement.
- La logique générée et la logique personnalisée divergent, et des cas limites commencent à échouer.
- Les mises à jour donnent l'impression d'être risquées parce que vous ne savez pas ce qui sera remplacé.
L'objectif n'est pas d'éviter la personnalisation, mais de rendre les reconstructions prévisibles. Si vous gardez une frontière propre entre les écrans générés et les widgets personnalisés, la régénération reste une opération de routine plutôt qu'une source de stress.
Une règle-frontière qui protège la régénération
Si vous voulez personnaliser sans perdre votre travail, suivez une règle : ne modifiez jamais les fichiers générés. Traitez-les comme une sortie en lecture seule, comme un artefact compilé.
Pensez votre UI comme deux zones :
- Zone générée : pages, layouts et écrans produits par le générateur.
- Zone personnalisée : vos composants Vue écrits à la main dans un dossier séparé.
L'UI générée doit consommer vos composants personnalisés. Elle ne doit pas être l'endroit où ces composants sont développés.
Pour que cela fonctionne sur le long terme, gardez la « frontière » petite et claire. Un widget personnalisé doit se comporter comme un petit produit avec un contrat :
- Props entrantes : uniquement ce dont il a besoin pour s'afficher.
- Événements sortants : uniquement ce à quoi la page doit réagir.
Évitez de toucher à l'état global ou d'appeler des API non liées depuis l'intérieur du widget, sauf si c'est explicitement prévu par le contrat.
Avec des écrans Vue3 générés à la manière d'AppMaster, cela signifie généralement que vous faites un câblage minimal dans l'écran généré pour passer des props et gérer des événements. Ce câblage peut changer lors des régénérations, mais il reste petit et facile à remettre en place. Le vrai travail reste sécurisé dans la zone personnalisée.
Modèles d'isolation adaptés à Vue3
L'objectif est simple : la régénération doit pouvoir remplacer librement les fichiers générés, tandis que votre code de widget reste intact.
Une approche pratique consiste à garder les widgets sur-mesure dans un petit module interne : composants, styles et utilitaires réutilisables au même endroit. Dans les applications Vue3 générées, cela signifie souvent que le code personnalisé vit en dehors des pages générées et est importé comme dépendance.
Un composant wrapper aide beaucoup. Laissez le wrapper communiquer avec l'application générée : lire la forme de données existante de la page, la normaliser et passer des props propres au widget. Si la forme de données générée change plus tard, vous mettez souvent à jour le wrapper plutôt que de réécrire le widget.
Quelques pratiques qui tiennent bien :
- Traitez le widget comme une boîte noire : props entrantes, événements sortants.
- Utilisez un wrapper pour mapper les réponses API, les dates et les identifiants dans des formats adaptés au widget.
- Scoppez les styles pour que les pages régénérées n'écrasent pas accidentellement votre widget.
- Ne comptez pas sur la structure DOM parente ou des classes spécifiques de la page.
Pour le style, préférez le CSS scoped (ou CSS Modules) et namespacez les classes partagées. Si le widget doit suivre le thème de l'application, transmettez des tokens de thème en props (couleurs, espacements, tailles) plutôt que d'importer les styles de la page.
Les slots peuvent être sûrs s'ils restent petits et optionnels, par exemple un message d'état vide. Si les slots commencent à contrôler la mise en page ou le comportement central, vous avez ramené le widget dans la couche générée — c'est là que la régénération devient douloureuse.
Concevoir un contrat de composant stable (props et événements)
La façon la plus sûre d'éviter les douleurs de régénération est de traiter chaque widget comme une interface stable. Les écrans générés peuvent changer. Votre composant ne devrait pas.
Commencez par les entrées (props). Gardez-les peu nombreuses, prévisibles et faciles à valider. Préférez les primitives simples et les objets plats que vous contrôlez. Ajoutez des valeurs par défaut pour que le widget se comporte correctement même si la page ne passe rien encore. Si quelque chose peut être mal formé (IDs, chaînes de dates, valeurs de type enum), validez et échouez en douceur : affichez un état vide au lieu de planter.
Pour les sorties, standardisez les événements pour que les widgets se comportent de façon cohérente dans l'application. Un ensemble fiable est :
update:modelValuepourv-modelchangepour les changements confirmés par l'utilisateur (pas chaque frappe)errorlorsque le composant ne peut pas accomplir sa tâchereadyquand un travail asynchrone est terminé et que le widget est utilisable
Si du travail asynchrone est impliqué, faites-en partie du contrat. Exposez des props loading et disabled, et envisagez errorMessage pour les échecs côté serveur. Si le composant récupère des données lui-même, émettez quand même error et ready pour que le parent puisse réagir (toast, journalisation, UI de repli).
Attentes d'accessibilité
Intégrez l'accessibilité dans le contrat. Acceptez une prop label (ou ariaLabel), documentez le comportement clavier et gardez le focus prévisible après les actions.
Par exemple, un widget timeline sur un tableau de bord devrait supporter les flèches pour naviguer entre les éléments, Entrée pour ouvrir les détails, et restituer le focus au contrôle qui a ouvert un dialogue lorsque ce dernier se ferme. Cela rend le widget réutilisable sur des écrans régénérés sans travail supplémentaire.
Étape par étape : ajouter un widget sur-mesure sans toucher aux fichiers générés
Commencez petit : un écran que les utilisateurs consultent, un widget qui lui donne du caractère. Garder le premier changement étroit facilite l'observation des effets de la régénération.
-
Créez le composant en dehors de la zone générée. Placez-le dans un dossier dont vous êtes responsable et versionnez-le (souvent
customouextensions). -
Gardez la surface publique petite. Quelques props entrantes, quelques événements sortants. Ne passez pas tout l'état de la page.
-
Ajoutez un wrapper fin que vous possédez. Son rôle : traduire « données de la page générée » en contrat du widget.
-
Intégrez via un point d'extension supporté. Référencez le wrapper d'une façon qui n'exige pas d'éditer les fichiers générés.
-
Régénérez et vérifiez. Votre dossier custom, le wrapper et le composant doivent rester inchangés et compiler.
Gardez des frontières nettes. Le widget s'occupe de l'affichage et de l'interaction. Le wrapper mappe les données et relaie les actions. Les règles métier restent dans la couche logique de l'application (backend ou processus partagés), pas enfouies dans le widget.
Un contrôle simple : si la régénération avait lieu maintenant, un coéquipier pourrait-il reconstruire l'application et obtenir le même résultat sans refaire des retouches manuelles ? Si oui, votre pattern est solide.
Où placer la logique pour que l'UI reste maintenable
Un widget personnalisé doit surtout se préoccuper de son rendu et de sa réponse aux interactions. Plus vous empilez de règles métier dans le widget, plus il devient difficile à réutiliser, tester et modifier.
Un bon défaut est : gardez la logique métier dans la page ou la couche feature, et gardez le widget « dumb ». La page décide quelles données fournir et ce qui doit se produire quand le widget émet un événement. Le widget rend et signale une intention utilisateur.
Quand vous avez besoin de logique proche du widget (formatage, petit état, validation côté client), cachez-la derrière une petite couche de service. En Vue3, cela peut être un module, un composable ou un store avec une API claire. Le widget importe cette API, pas des internals aléatoires de l'app.
Une séparation pratique :
- Widget (composant) : état UI, gestion des entrées, visuels, émissions d'événements comme
select,change,retry. - Service/composable : mise en forme des données, cache, mappage des erreurs API vers des messages utilisateurs.
- Page/container : règles métier, permissions, quelles données charger et quand sauvegarder.
- Parties générées de l'app : laissées intactes ; passez les données et écoutez les événements.
Évitez les appels API directs dans le widget sauf si c'est explicitement son contrat. S'il possède la récupération, rendez-le évident (nommez-le par ex. CustomerSearchWidget et centralisez l'appel dans un service). Sinon, passez items, loading et error en props.
Les messages d'erreur doivent être orientés utilisateur et cohérents. Au lieu d'afficher le texte brut du serveur, mappez les erreurs vers un petit jeu de messages utilisés partout, par exemple « Impossible de charger les données. Réessayez. ». Incluez une action de retry quand c'est possible, et journalisez les erreurs détaillées hors du widget.
Exemple : un widget ApprovalBadge personnalisé ne devrait pas décider si une facture est approuvable. Laissez la page calculer status et canApprove. Le badge émet approve, et la page exécute la règle réelle et appelle le backend (par exemple l'API que vous modelez dans AppMaster), puis renvoie l'état de succès ou d'erreur propre dans l'UI.
Erreurs courantes qui causent des problèmes après la régénération
La plupart des problèmes ne viennent pas de Vue. Ils viennent du mélange du travail personnalisé dans des zones possédées par le générateur, ou de la dépendance à des détails qui vont certainement changer.
Les erreurs qui transforment le plus souvent un petit widget en nettoyage récurrent :
- Modifier directement des fichiers Vue générés et oublier ce qui a changé.
- Utiliser du CSS global ou des sélecteurs larges qui affectent silencieusement d'autres écrans quand le balisage change.
- Lire ou muter directement des structures d'état générées, si bien qu'un renommage casse le widget.
- Regrouper trop d'hypothèses spécifiques à une page dans un seul composant.
- Changer l'API du composant (props/événements) sans plan de migration.
Un scénario classique : vous ajoutez un widget de tableau personnalisé et ça marche. Un mois plus tard, un changement de layout généré fait qu'une règle globale .btn affecte maintenant les pages de login et d'admin. Ou un objet de données change de user.name à user.profile.name et le widget échoue silencieusement. Le widget n'était pas le problème : la dépendance à des détails instables l'était.
Deux habitudes évitent la plupart de ces soucis :
Premièrement, traitez le code généré comme lecture seule et gardez les fichiers personnalisés séparés, avec une frontière d'import claire.
Deuxièmement, maintenez le contrat de composant petit et explicite. Si vous devez l'évoluer, ajoutez une prop de version simple (par ex. apiVersion) ou supportez les anciennes et nouvelles formes de props pendant une release.
Checklist rapide avant de livrer un composant personnalisé
Avant de merger un widget sur-mesure dans une app Vue3 générée, faites un rapide contrôle de réalité. Il doit survivre à la prochaine régénération sans exploits, et quelqu'un d'autre doit pouvoir le réutiliser.
- Test de régénération : lancez une régénération complète et rebuild. Si vous avez dû rééditer un fichier généré, la frontière est mauvaise.
- Entrées et sorties claires : props in, emits out. Évitez les dépendances magiques comme manipuler le DOM externe ou compter sur un store de page spécifique.
- Contention des styles : scopssez les styles et utilisez un préfixe de classe clair (par exemple
timeline-). - Tous les états gérés : loading, error et empty doivent exister et être sensés.
- Réutilisable sans clonage : vérifiez que vous pouvez l'utiliser sur une autre page en changeant props et handlers, pas en copiant le code interne.
Un moyen simple de valider : imaginez ajouter le widget à un écran admin puis à un portail client. Si les deux fonctionnent avec seulement des changements de props et de gestion d'événements, vous êtes dans une bonne zone.
Exemple réaliste : ajouter un widget timeline à un tableau de bord
Une équipe de support veut souvent un écran qui raconte l'histoire d'un ticket : changements de statut, notes internes, réponses client, événements de paiement ou de livraison. Un widget timeline convient bien, mais vous ne voulez pas modifier les fichiers générés et perdre votre travail à la prochaine régénération.
L'approche sûre est de garder le widget isolé en dehors de l'UI générée et de l'insérer dans la page via un wrapper fin.
Contrat du widget
Gardez-le simple et prévisible. Par exemple, le wrapper passe :
ticketId(string)range(7 derniers jours, 30 derniers jours, personnalisé)mode(compact vs détaillé)
Le widget émet :
selectquand l'utilisateur clique un événementchangeFiltersquand l'utilisateur ajuste la plage ou le mode
Ainsi, le widget ne sait rien du dashboard, des modèles de données ou de la façon dont les requêtes sont faites. Il rend une timeline et signale les actions utilisateur.
Comment le wrapper le connecte à la page
Le wrapper se place à côté du dashboard et traduit les données de la page en contrat : il lit l'ID du ticket depuis l'état de la page, convertit les filtres UI en range et mappe les enregistrements backend au format attendu par le widget.
Quand le widget émet select, le wrapper peut ouvrir un panneau de détails ou déclencher une action de page. Quand il émet changeFilters, le wrapper met à jour les filtres de page et rafraîchit les données.
Quand l'UI du dashboard est régénérée, le widget reste intact car il vit en dehors des fichiers générés. Le plus souvent, vous ne touchez que le wrapper si la page renomme un champ ou change la façon dont elle stocke les filtres.
Tests et habitudes de release pour éviter les surprises
Les composants personnalisés cassent généralement de façon ennuyeuse : une forme de prop change, un événement cesse de remonter, ou une page générée rerend plus souvent que prévu. Quelques habitudes détectent ces problèmes tôt.
Tests locaux : détecter vite les ruptures de frontière
Traitez la frontière entre UI générée et widget comme une API. Testez le widget sans l'app complète d'abord, en utilisant des props codées en dur qui respectent le contrat.
Rendez-le avec des props « chemin heureux » et avec des valeurs manquantes. Simulez les événements clés (save, cancel, select) et vérifiez que le parent les gère. Testez la lenteur des données et les petites tailles d'écran. Vérifiez qu'il n'écrit pas dans l'état global à moins que ce soit explicitement prévu.
Si vous travaillez sur une app Vue3 générée par AppMaster, faites ces contrôles avant de lancer une régénération. Il est plus facile de diagnostiquer une rupture de frontière quand vous n'avez pas changé deux choses à la fois.
Régression après régénération : quoi vérifier en premier
Après chaque régénération, reverifiez les points de contact : les mêmes props sont-elles toujours passées, et les mêmes événements sont-ils toujours gérés ? C'est là que la casse apparaît d'abord.
Gardez l'inclusion prévisible. Évitez des imports fragiles dépendant de chemins de fichiers qui peuvent bouger. Utilisez un point d'entrée stable pour vos composants personnalisés.
Pour la production, ajoutez un logging léger et de la capture d'erreurs à l'intérieur du widget :
- Monté avec les props clés (sanitisées)
- Violations de contrat (prop requise manquante, type incorrect)
- Appels API échoués avec un code d'erreur court
- États vides inattendus
Quand quelque chose casse, vous voulez répondre rapidement : la régénération a-t-elle changé les entrées, ou le widget a-t-il changé ?
Étapes suivantes : rendre le pattern reproductible dans l'app
Quand le premier widget fonctionne, le vrai gain est de rendre le pattern reproductible pour que le suivant ne soit pas un bricolage.
Créez une petite norme interne pour les contrats de widget et documentez-la là où votre équipe garde ses notes. Gardez-la simple : conventions de nommage, props requises vs optionnelles, un petit ensemble d'événements, comportement d'erreur et responsabilité claire (ce qui vit dans l'UI générée vs votre dossier custom).
Écrivez aussi les règles-frontière en langage clair : ne pas éditer les fichiers générés, isoler le code personnalisé, et ne transmettre les données que via props et événements. Cela empêche le « correctif rapide » qui devient une taxe de maintenance permanente.
Avant de bâtir un second widget, faites un petit essai de régénération. Déployez le premier widget, puis régénérez au moins deux fois lors de changements normaux (un changement d'étiquette, un changement de layout, un nouveau champ) et confirmez que rien ne casse.
Si vous utilisez AppMaster, il est souvent préférable de garder la majeure partie de l'UI et de la logique dans les éditeurs visuels (UI builders, Business Process Editor et Data Designer). Réservez les composants Vue personnalisés aux widgets vraiment sur-mesure que les éditeurs ne peuvent pas exprimer, comme des timelines spécialisées, des interactions graphiques ou des contrôles d'entrée inhabituels. Pour un point de départ propre, AppMaster sur appmaster.io est conçu autour de la régénération, donc isoler les widgets personnalisés devient une partie naturelle du flux de travail.
FAQ
Modifier les fichiers Vue générés est risqué car la régénération peut les écraser complètement. Même si votre changement survit une fois, une petite modification visuelle dans l'éditeur peut recréer les templates et effacer vos ajustements manuels.
Placez tout le code Vue écrit à la main dans un dossier séparé et maîtrisé (par exemple custom ou extensions) et importez-le comme dépendance. Traitez les pages générées comme un artefact en lecture seule et ne connectez vos composants que via une interface petite et stable.
Un wrapper est un composant mince que vous possédez et qui se place entre une page générée et votre widget. Il traduit la structure de données de la page en props propres et transforme les événements du widget en actions de page : si les données générées changent, vous mettez souvent à jour seulement le wrapper.
Gardez le contrat petit : quelques props pour les données nécessaires et quelques événements pour rapporter l'intention utilisateur. Préférez des valeurs simples et des objets que vous contrôlez, ajoutez des valeurs par défaut, validez les entrées et gérez les erreurs en douceur (état vide) plutôt que de planter.
update:modelValue est adapté quand le widget se comporte comme un contrôle de formulaire et doit supporter v-model. change est préférable pour les actions « confirmées », comme Cliquer sur Sauvegarder ou terminer une sélection, afin qu'un parent ne traite pas chaque frappe.
Scopez vos styles et utilisez un préfixe de classe clair pour que les pages régénérées ne puissent pas écraser accidentellement votre CSS. Si le widget doit correspondre au thème de l'application, transmettez des tokens de thème en props (couleurs, espacements, tailles de police) plutôt que d'importer ou dépendre des styles de page.
Par défaut, gardez les règles métier en dehors du widget. Laissez la page ou le backend décider des permissions, validations et comportements de sauvegarde, tandis que le widget se concentre sur le rendu et l'interaction et émet des événements comme select, retry ou approve.
Évitez de dépendre de détails instables comme des chemins de fichiers générés, la structure DOM parente ou la forme interne des objets d'état. Si vous devez les utiliser, cachez-les dans un wrapper afin qu'un renommage (user.name → user.profile.name) n'oblige pas à réécrire le widget.
Testez le widget isolément avec des props codées en dur correspondant au contrat, y compris des valeurs manquantes ou mal formées. Après la régénération, vérifiez d'abord si les mêmes props sont passées et si les mêmes événements sont gérés : c'est là que la casse apparaît souvent.
Ne construisez un widget sur mesure que pour ce que les éditeurs visuels ne peuvent pas exprimer facilement : graphiques complexes, sélecteurs de carte, pads de signature, ou outils de drag-and-drop. Si un besoin peut être couvert par l'UI ou les processus générés, cela restera généralement plus maintenable à long terme.


