Skip to content

Évolution future vers un module Nuxt 4

La phase actuelle publie QForm Builder comme Nuxt Layer npm.

La prochaine évolution naturelle consiste à créer un vrai module Nuxt 4 :

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

  qformBuilder: {
    prefix: '',
    installPinia: true,
    installFormKit: true,
    installQuasar: true,
    injectCss: true
  }
})

Références officielles :

Différence entre layer et module

SujetNuxt LayerModule Nuxt 4
Activationextendsmodules
Effort actuelfaibleplus élevé
Contrôle des optionslimitéexcellent
Isolation des routes de démopossiblenaturelle
Installation automatique de moduleshéritée du layercontrôlée par installModule()
Préfixe de composantspeu pratiquesimple via addComponentsDir()
API publiqueconventionnelleexplicite
Meilleur choix long termebonexcellent

Pourquoi ne pas convertir tout de suite ?

Le projet est déjà exploitable comme layer.

La conversion en module demande une migration plus profonde :

txt
src/
├── module.ts
└── runtime/
    ├── assets/
    ├── components/
    ├── composables/
    ├── constants/
    ├── stores/
    ├── types/
    └── utils/

Cela implique aussi de remplacer progressivement les imports internes trop liés au layer.

Structure cible du futur module

txt
nuxt-qform-builder/
├── src/
│   ├── module.ts
│   └── runtime/
│       ├── assets/
│       ├── components/
│       ├── composables/
│       ├── constants/
│       ├── stores/
│       ├── types/
│       └── utils/
├── playground/
│   ├── app.vue
│   └── pages/
├── docs/
├── package.json
└── README.md

Options cible

ts
export interface QFormBuilderModuleOptions {
  prefix: string
  installPinia: boolean
  installFormKit: boolean
  installQuasar: boolean
  injectCss: boolean
  exposeTypes: boolean
}

Exemple cible de module.ts

ts
import {
  addComponentsDir,
  addImportsDir,
  createResolver,
  defineNuxtModule,
  installModule
} from '@nuxt/kit'

export interface QFormBuilderModuleOptions {
  prefix: string
  installPinia: boolean
  installFormKit: boolean
  installQuasar: boolean
  injectCss: boolean
}

export default defineNuxtModule<QFormBuilderModuleOptions>({
  meta: {
    name: '@vevedh/nuxt-qform-builder',
    configKey: 'qformBuilder',
    compatibility: {
      nuxt: '^4.0.0'
    }
  },

  defaults: {
    prefix: '',
    installPinia: true,
    installFormKit: true,
    installQuasar: true,
    injectCss: true
  },

  async setup(options, nuxt) {
    const resolver = createResolver(import.meta.url)

    if (options.installPinia) {
      await installModule('@pinia/nuxt')
    }

    if (options.installFormKit) {
      await installModule('@formkit/nuxt')
    }

    if (options.installQuasar) {
      await installModule('nuxt-quasar-ui', {
        lang: 'fr',
        iconSet: 'material-icons',
        plugins: ['Dialog', 'Loading', 'Notify']
      })
    }

    addComponentsDir({
      path: resolver.resolve('./runtime/components'),
      prefix: options.prefix,
      pathPrefix: false,
      global: true
    })

    addImportsDir(resolver.resolve('./runtime/composables'))
    addImportsDir(resolver.resolve('./runtime/stores'))
    addImportsDir(resolver.resolve('./runtime/utils'))

    if (options.injectCss) {
      nuxt.options.css.push(resolver.resolve('./runtime/assets/scss/index.scss'))
    }
  }
})

Refactors nécessaires avant module

1. Stabiliser les imports internes

Dans un layer, ~ pointe naturellement vers la source du layer pendant son développement.

Dans un module, le runtime est publié dans src/runtime. Il faut donc privilégier :

ts
import type { FormBuilderSchema } from '../types/form-builder'

ou créer des alias de module :

ts
#qform-builder/types
#qform-builder/runtime

2. Exporter les types publics

Objectif futur :

ts
import type {
  FormBuilderSchema,
  FormBuilderValues,
  FormBuilderSavePayload
} from '@vevedh/nuxt-qform-builder/types'

3. Rendre le catalogue extensible

Le module doit exposer une option :

ts
qformBuilder: {
  catalog: [],
  configPanels: {}
}

ou un système de plugin runtime :

ts
export default defineNuxtPlugin(() => {
  defineQFormElement({
    type: 'nfz-service-select',
    label: 'Sélecteur service NFZ',
    schema: {},
    configPanel: () => import('./NfzServiceSelectConfig.vue')
  })
})

4. Séparer builder et viewer

Le futur module pourra permettre :

ts
qformBuilder: {
  components: {
    builder: true,
    viewer: true
  }
}

Cela permettra à une app runtime légère d’installer seulement FormViewer.

Roadmap module

Phase M1 — packaging module minimal

  • créer src/module.ts ;
  • déplacer app/* vers src/runtime/* ;
  • enregistrer composants, composables et stores ;
  • garder un playground de test ;
  • publier sous @vevedh/nuxt-qform-builder.

Phase M2 — options configurables

  • prefix ;
  • injectCss ;
  • installQuasar ;
  • installFormKit ;
  • installPinia ;
  • activation séparée builder/viewer.

Phase M3 — parité Vueform Builder

  • custom elements ;
  • custom config panels ;
  • catalog public ;
  • conditions avancées ;
  • validation avancée ;
  • i18n ;
  • theming ;
  • export/import UI ;
  • génération depuis Zod/NFZ/IA.

Convention à conserver

La phase layer npm doit rester stable tant que le module n’est pas publié.

Le module Nuxt 4 devra être une évolution contrôlée, pas un remplacement brutal :

txt
@vevedh/qform-builder-layer      # phase actuelle stable
@vevedh/nuxt-qform-builder       # évolution future module Nuxt 4

QForm Builder — couche Nuxt 4 / Quasar / FormKit réutilisable.