Skip to content

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.

ts
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 :

txt
app/
├── assets/
├── components/
├── composables/
├── constants/
├── stores/
├── types/
└── utils/

nuxt.config.ts
formkit.config.ts
.playground/

Cette structure permet de partager :

  • les composants FormBuilder et FormViewer ;
  • 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

txt
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.md

Les 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 :

txt
/autonome
/multi-instance
/viewer

nuxt.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.

ts
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 :

ts
ssr: false

Ce choix appartient à l’application hôte.

Pour un usage sûr avec SSR activé, envelopper FormBuilder dans ClientOnly :

vue
<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 :

json
{
  "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 :

json
{
  "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 :

bash
bun install
bun run docs:build
bun run typecheck
bun run pack:dry-run

Le script typecheck exécute automatiquement nuxi prepare .playground avant vue-tsc. Pour un contrôle local suivi du démarrage :

powershell
bun run clean && bun i && bun run typecheck && bun dev

La commande importante avant publication est :

bash
npm pack --dry-run

Elle 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 :

bash
bun run quasar-adapters:check
bun run e2e:static:check
bun run e2e:visual:update
bun run e2e:visual

Le 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 :

bash
bun run quasar-advanced:check
bun run quasar-advanced:runtime:check
bun run drop-indicator:check
bun run e2e:static:check
bun run e2e:visual

Les 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 :

bash
npm login

Publication d’un package scoped public :

bash
bun run npm:publish

Le package contient déjà :

json
{
  "publishConfig": {
    "access": "public"
  }
}

Donc la commande suivante suffit aussi dans la majorité des cas :

bash
bun run npm:publish

Référence npm : https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/

Installation dans une application Nuxt 4

bash
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/utils

Puis :

ts
export default defineNuxtConfig({
  extends: ['@vevedh/qform-builder-layer'],

  compatibilityDate: '2026-05-08'
})

Exemple minimal

vue
<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 :

vue
<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 :

txt
FormBuilder
FormViewer
@vevedh/qform-builder-layer/types

Les 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 :

bash
bun run pack:dry-run

Commandes finales recommandées

bash
bun run clean
bun install
bun run release:doctor
bun run npm:publish:dry-run

La publication réelle reste :

bash
npm login
bun run npm:publish

Version 0.1.5 — contrôle QTooltip généralisé

Avant publication, vérifier le contrat partagé et l'absence de doublon QBtn :

bash
bun run tooltip:check
bun run tooltip:runtime:check
bun run quasar-adapters:check
bun run e2e:static:check
bun run e2e:visual

Dans /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 :

html
<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é :

ts
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 :

bash
bun run e2e:visual:update
bun run e2e:visual

Le 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.

ts
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.

bash
bun run quasar-adapters:check
bun run tooltip:check
bun run e2e:static:check
bun run e2e:visual:update
bun run e2e:visual

Version 0.1.8 — contrat clair/sombre

La publication 0.1.8 ajoute deux gates obligatoires :

bash
bun run dark-mode:check
bun run dark-mode:runtime:check

Le 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 :

bash
bun run templates:check
bun run templates:runtime:check

Le 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 :

bash
bun run typecheck
bun run runtime:build:check
bun run docs:build
bun run e2e:visual
npm pack --dry-run

Le 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 :

bash
bun install
bun run accessibility:check
bun run release:browser:check
npm pack --dry-run

L’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 :

bash
bun run ci:quality
bun run e2e:ci:functional
bun run release:browser:check
bun run pack:artifact

Le 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.

QForm Builder — layer Nuxt 4, Quasar et FormKit pour formulaires dynamiques.