Publier QForm Builder comme Nuxt Layer npm
Cette phase transforme QForm Builder en Nuxt Layer publiable sur npm.
L’objectif est de pouvoir installer le package dans une nouvelle application Nuxt 4 ou dans une application Nuxt 4 existante, puis de l’activer avec extends.
export default defineNuxtConfig({
extends: ['@vevedh/qform-builder-layer']
})Référence officielle : https://nuxt.com/docs/4.x/guide/going-further/layers
Pourquoi publier d’abord comme layer ?
Le projet est déjà naturellement structuré comme une couche Nuxt :
app/
├── assets/
├── components/
├── composables/
├── constants/
├── stores/
├── types/
└── utils/
nuxt.config.ts
formkit.config.ts
.playground/Cette structure permet de partager :
- les composants
FormBuilderetFormViewer; - les composants Quasar/FormKit internes ;
- les composables ;
- les stores Pinia ;
- les types TypeScript ;
- les styles SCSS ;
- la configuration Nuxt utile au builder.
Structure retenue pour la phase 1
qform-builder-layer/
├── app/
│ ├── assets/
│ ├── components/
│ ├── composables/
│ ├── constants/
│ ├── stores/
│ ├── types/
│ └── utils/
├── .playground/
│ └── app/
│ ├── app.vue
│ ├── layouts/
│ └── pages/
├── docs/
├── formkit.config.ts
├── nuxt.config.ts
├── package.json
└── README.mdLes pages de démonstration sont volontairement déplacées dans .playground/app/pages.
Elles ne doivent pas être publiées dans le layer runtime, car une application consommatrice ne doit pas recevoir automatiquement les routes de démonstration :
/autonome
/multi-instance
/viewernuxt.config.ts du layer
Le layer doit rester le plus neutre possible.
Il fournit les modules nécessaires, les styles, Quasar et FormKit, mais il ne doit pas imposer de choix globaux dangereux à l’application hôte.
const layerStylesheetUrl = new URL(
'./app/assets/scss/index.scss',
import.meta.url,
)
const layerStylesheet = decodeURIComponent(layerStylesheetUrl.pathname)
.replace(/^\/([A-Za-z]:\/)/, '$1')
export default defineNuxtConfig({
modules: [
'nuxt-quasar-ui',
'@pinia/nuxt',
'@formkit/nuxt'
],
formkit: {
configFile: normalizeLayerFileUrl(
new URL('./formkit.config.ts', import.meta.url),
),
defaultConfig: true,
autoImport: false,
},
css: [layerStylesheet],
vite: {
optimizeDeps: {
include: [
'@formkit/core',
'@formkit/i18n',
'@formkit/utils',
'shiki'
]
}
},
quasar: {
lang: 'fr',
iconSet: 'material-icons',
plugins: [
'BottomSheet',
'Dialog',
'Loading',
'LoadingBar',
'Notify',
'Dark',
'LocalStorage',
'Platform'
]
}
})Le stylesheet et formkit.config.ts doivent être résolus depuis import.meta.url. Il ne faut pas utiliser ~/assets/... dans le nuxt.config.ts publié : dans le playground ou une application consommatrice, ~ désigne l'application hôte et non le layer.
Cette variante n'importe pas node:url : elle reste donc compatible avec le tsconfig généré du playground même si @types/node n'est pas installé directement. La normalisation retire uniquement le slash initial des chemins Windows de type /C:/....
Le layer ne doit pas définir :
ssr: falseCe choix appartient à l’application hôte.
Pour un usage sûr avec SSR activé, envelopper FormBuilder dans ClientOnly :
<template>
<ClientOnly>
<FormBuilder
builder-id="admin-contact-builder"
v-model:schema="schema"
v-model:values="values"
/>
</ClientOnly>
</template>FormViewer peut être rendu plus facilement côté runtime, mais il reste recommandé de tester le comportement selon les composants Quasar/FormKit utilisés.
package.json de publication
Le package utilise :
{
"name": "@vevedh/qform-builder-layer",
"main": "./nuxt.config.ts",
"publishConfig": {
"access": "public"
},
"files": [
"app/assets",
"app/components",
"app/composables",
"app/constants",
"app/stores",
"app/types",
"app/quasar-options",
"app/types.ts",
"app/utils",
"docs",
"formkit.config.ts",
"nuxt.config.ts",
"README.md",
"LICENSE"
]
}Le champ files est essentiel : il évite de publier .playground, .nuxt, .output, node_modules, les caches et les fichiers de patch internes.
Dépendances peer
QForm Builder est un layer d’intégration. Les dépendances principales sont donc déclarées comme peerDependencies :
{
"peerDependencies": {
"nuxt": "^4.0.0",
"vue": "^3.5.0",
"pinia": "^3.0.0",
"@pinia/nuxt": "^0.11.0",
"quasar": "^2.18.0",
"@quasar/extras": "^1.17.0",
"nuxt-quasar-ui": "^2.1.0",
"@formkit/core": "^2.1.0",
"@formkit/i18n": "^2.1.0",
"@formkit/inputs": "^2.1.0",
"@formkit/nuxt": "^2.1.0",
"@formkit/utils": "^2.1.0",
"@formkit/vue": "^2.1.0"
}
}Cela laisse l’application hôte garder la maîtrise de ses versions Nuxt, Vue, Quasar et FormKit.
Tester le package avant publication
Depuis le dépôt du layer :
bun install
bun run docs:build
bun run typecheck
bun run pack:dry-runLe script typecheck exécute automatiquement nuxi prepare .playground avant vue-tsc. Pour un contrôle local suivi du démarrage :
bun run clean && bun i && bun run typecheck && bun devLa commande importante avant publication est :
npm pack --dry-runElle permet de vérifier exactement les fichiers qui seront publiés.
Version 0.1.3 — contrôle QBtn
Avant la publication 0.1.3, vérifier également le panneau du bouton et son infobulle :
bun run quasar-adapters:check
bun run e2e:static:check
bun run e2e:visual:update
bun run e2e:visualLe scénario doit confirmer qu’un seul champ Libellé du bouton est présent et que info apparaît sous forme de QTooltip au survol du QBtn.
Version 0.1.2 et tests visuels
Avant la publication 0.1.2, exécuter également :
bun run quasar-advanced:check
bun run quasar-advanced:runtime:check
bun run drop-indicator:check
bun run e2e:static:check
bun run e2e:visualLes captures Playwright appartiennent au dépôt de développement et ne sont pas publiées dans le package runtime. Le sous-chemin @vevedh/qform-builder-layer/quasar-options est, lui, inclus dans le tarball npm.
Publier sur npm
Connexion :
npm loginPublication d’un package scoped public :
bun run npm:publishLe package contient déjà :
{
"publishConfig": {
"access": "public"
}
}Donc la commande suivante suffit aussi dans la majorité des cas :
bun run npm:publishRéférence npm : https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/
Installation dans une application Nuxt 4
bun add @vevedh/qform-builder-layer \
nuxt-quasar-ui @pinia/nuxt @formkit/nuxt \
quasar @quasar/extras \
@formkit/core @formkit/i18n @formkit/inputs @formkit/vue @formkit/utilsPuis :
export default defineNuxtConfig({
extends: ['@vevedh/qform-builder-layer'],
compatibilityDate: '2026-05-08'
})Exemple minimal
<script setup lang="ts">
import type { FormKitSchemaDefinition } from '@formkit/core'
type FormBuilderSchema = FormKitSchemaDefinition[]
type FormBuilderValues = Record<string, unknown>
const schema = ref<FormBuilderSchema>([])
const values = ref<FormBuilderValues>({})
</script>
<template>
<ClientOnly>
<FormBuilder
builder-id="contact-form-builder"
v-model:schema="schema"
v-model:values="values"
autosave
storage-key="contact-form-builder"
/>
</ClientOnly>
</template>Bonne pratique
Dans une application métier, ne pas utiliser builder-id="default".
Utiliser une clé stable :
<FormBuilder :builder-id="`workspace-${workspaceId}-form-${formId}`" />Cela garantit l’isolation des stores, de l’historique, de l’autosave et des offsets UI.
Contrat Community finalisé
La phase 1 considère comme stable le contrat suivant :
FormBuilder
FormViewer
@vevedh/qform-builder-layer/typesLes fichiers de démonstration, le contexte projet et les journaux de patch ne sont pas publiés sur npm.
Pour vérifier cette règle :
bun run pack:dry-runCommandes finales recommandées
bun run clean
bun install
bun run release:doctor
bun run npm:publish:dry-runLa publication réelle reste :
npm login
bun run npm:publishVersion 0.1.5 — contrôle QTooltip généralisé
Avant publication, vérifier le contrat partagé et l'absence de doublon QBtn :
bun run tooltip:check
bun run tooltip:runtime:check
bun run quasar-adapters:check
bun run e2e:static:check
bun run e2e:visualDans /quasar-advanced, passer en aperçu puis survoler QEditor, QSelect, QRating, QInput et le paragraphe statique. Chaque propriété info doit afficher exactement un QTooltip. Les quatre variantes de boutons Community doivent toutes conserver buttonLabel et leur QTooltip enfant direct de QBtn.
Version 0.1.6 — fiabilité des scénarios visuels
Les contrôles statiques ne remplacent pas l'exécution navigateur. Les scénarios Playwright utilisent désormais les marqueurs stables suivants :
<div data-qform-preview-mode="previewing" aria-label="Aperçu"></div>
<main data-qform-e2e-page="qtooltip-audit"></main>QTab expose le rôle ARIA tab et ne doit pas être recherché comme un bouton. Les assertions d'infobulle doivent cibler l'overlay Quasar téléporté :
const tooltip = page.locator('.q-tooltip').filter({ hasText: expectedText }).last()
await expect(tooltip).toBeVisible()Pour créer les captures de référence puis les vérifier :
bun run e2e:visual:update
bun run e2e:visualLe helper E2E attend aussi la compilation à froid des routes /quasar-advanced et /qtooltip-audit, avec une seule nouvelle tentative contrôlée en cas de rechargement Vite transitoire.
Version 0.1.7 — propriétés FormKit promues
FormKit place les propriétés enregistrées dans node.define({ props: [...] }) directement sur le contexte du composant. Un adapter Quasar ne doit donc pas lire exclusivement context.attrs.
const attrs = resolveQFormBuilderFormKitAttrs(context, [
'buttonLabel',
'info',
'description',
])Avant publication, vérifier que les quatre boutons de /quasar-advanced possèdent un libellé, un marqueur data-qform-field-name et un seul QTooltip. Vérifier aussi les infobulles dédiées des structures dans /qtooltip-audit.
bun run quasar-adapters:check
bun run tooltip:check
bun run e2e:static:check
bun run e2e:visual:update
bun run e2e:visualVersion 0.1.8 — contrat clair/sombre
La publication 0.1.8 ajoute deux gates obligatoires :
bun run dark-mode:check
bun run dark-mode:runtime:checkLe build de release doit aussi exécuter les scénarios Playwright de /dark-mode-audit. Le thème sérialisé ne doit pas être modifié lorsque Quasar change d’apparence ; la palette sombre est dérivée uniquement pour le rendu.
Version 0.1.9 — registre de modèles
La publication 0.1.9 ajoute le sous-chemin public ./templates et deux gates obligatoires :
bun run templates:check
bun run templates:runtime:checkLe contrôle runtime doit couvrir au minimum la fusion par clé, l’assainissement des données, le rejet des clés de pollution de prototype, les collisions de noms, la réécriture des conditions et validations croisées, les valeurs initiales, le conflit de stepper et la conservation du thème actif.
Avant publication, ouvrir /templates, vérifier la recherche, l’ajout, le remplacement confirmé et l’infobulle Quasar du bouton d’ajout indisponible, puis exécuter :
bun run typecheck
bun run runtime:build:check
bun run docs:build
bun run e2e:visual
npm pack --dry-runLe tarball doit inclure app/templates/** et exclure .playground, tests, patch-memory, AGENTS.md et PROMPT_CONTEXT.md.
Publication 0.1.16 — accessibilité
Le tarball doit inclure app/accessibility/** et exposer @vevedh/qform-builder-layer/accessibility. Avant publication :
bun install
bun run accessibility:check
bun run release:browser:check
npm pack --dry-runL’audit navigateur ne met jamais à jour les captures de référence. Toute nouvelle baseline doit être générée séparément, inspectée puis commitée.
Publication 0.1.17 — matrice CI et artifact vérifié
Avant une release candidate, le dépôt doit désormais produire le même contrat sur Windows et Linux :
bun run ci:quality
bun run e2e:ci:functional
bun run release:browser:check
bun run pack:artifactLe job package conserve le .tgz et package-manifest.json comme artifacts GitHub Actions. Le manifeste contient l’identité, la taille, l’intégrité npm et le SHA-256 du tarball. pack:verify refuse les fixtures, workflows, tests, lockfiles et journaux internes dans le package publié.
Le build tests/host-app complète le playground : il vérifie une consommation SSR réelle du layer et des sous-chemins publics avant publication.