Skip to content

Evolution to a Nuxt 4 module

The current distribution is an npm Nuxt Layer. The recommended next distribution is a real Nuxt 4 module under a new name, while retaining the layer as a compatibility reference.

Recommended technical name:

txt
@vevedh/nuxt-form-builder

npm and trademark availability must be checked before publication.

Migration decision

The project can start the module phase because the public contract is frozen, FormBuilder and FormViewer are clearly identified, pure engines are exported, an independent host application is built, browser/multi-OS tests exist, and no backend is mandatory.

The module should not be published as stable immediately. The first milestone is a packaging migration without functional changes.

Layer versus module

TopicNuxt LayerNuxt 4 module
Activationextendsmodules
Configurationconfig mergingtyped configKey options
Public componentslayer scanexplicit registration
Auto-importsinherited directoriesNuxt Kit registration
Nuxt dependenciesinheritedmoduleDependencies
Distributionlayer sourcescompiled module/runtime
Long-term rolecompatibilityprimary distribution

Target structure

txt
packages/nuxt-form-builder/
├── src/
│   ├── module.ts
│   └── runtime/
│       ├── assets/
│       ├── components/
│       ├── composables/
│       ├── constants/
│       ├── stores/
│       ├── types/
│       └── utils/
├── playground/
├── test/
├── docs/
└── package.json

A multi-package architecture may extract pure engines into qform-core and retain a temporary layer wrapper.

Target options

ts
export interface NuxtFormBuilderModuleOptions {
  prefix?: string
  locale?: 'fr' | 'en'
  injectCss?: boolean
  components?: {
    builder?: boolean
    viewer?: boolean
  }
  dependencies?: {
    pinia?: boolean
    formkit?: boolean
    quasar?: boolean
    unocss?: boolean
  }
}

Modern module entrypoint

Module dependencies should be declared with moduleDependencies; installModule() is now deprecated in Nuxt Kit.

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

export interface NuxtFormBuilderModuleOptions {
  prefix?: string
  locale?: 'fr' | 'en'
  injectCss?: boolean
  components?: {
    builder?: boolean
    viewer?: boolean
  }
}

export default defineNuxtModule<NuxtFormBuilderModuleOptions>({
  meta: {
    name: '@vevedh/nuxt-form-builder',
    configKey: 'formBuilder',
    compatibility: { nuxt: '^4.0.0' },
  },

  defaults: {
    prefix: '',
    locale: 'fr',
    injectCss: true,
    components: { builder: true, viewer: true },
  },

  moduleDependencies: {
    '@pinia/nuxt': { version: '>=0.11.0' },
    '@formkit/nuxt': { version: '>=2.1.0 <3.0.0' },
    'nuxt-quasar-ui': { version: '>=2.1.0' },
    '@unocss/nuxt': { version: '>=66.0.0' },
  },

  setup(options, nuxt) {
    const resolver = createResolver(import.meta.url)
    const runtime = resolver.resolve('./runtime')
    const prefix = options.prefix || ''

    if (options.components?.builder !== false) {
      addComponent({
        name: `${prefix}FormBuilder`,
        filePath: resolver.resolve(runtime, 'components/FormBuilder.vue'),
      })
    }

    if (options.components?.viewer !== false) {
      addComponent({
        name: `${prefix}FormViewer`,
        filePath: resolver.resolve(runtime, 'components/FormViewer.vue'),
      })
    }

    addImportsDir(resolver.resolve(runtime, 'composables'))

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

The example illustrates the module boundary; FormKit configuration and internal component registration need a dedicated strategy before publication.

Required refactors

  1. Replace FormKit adapter imports from layer #components with generated or direct runtime registration.
  2. Replace inherited layer auto-imports with explicit Nuxt Kit registration or direct internal imports.
  3. Globally register only FormBuilder and FormViewer; keep 85 internal components private.
  4. Resolve styles and runtime paths through createResolver(import.meta.url).
  5. Publish compiled module/runtime output and preserve or re-export current public subpaths.
  6. Test installed tarballs in clean Nuxt applications on SSR, Windows and Linux.

Features already complete

The module migration must preserve rather than redevelop catalog/config panels, conditions, validation, templates, Theme Builder, history, tree, migrations, i18n, accessibility and import/export.

Phase 3 features — Zod/NFZ, CRUD generation, business packs, code export, headless and guided generation — should begin only after module parity.

Module roadmap

  • M0: reproducible layer reference, lockfile, remote CI, real screenshots/baselines.
  • M1: official module skeleton and runtime migration without feature changes.
  • M2: FormKit, Quasar, Pinia, UnoCSS, styles, types and public subpaths.
  • M3: prefix, locale, CSS and separate Builder/Viewer options.
  • M4: installed-tarball, SSR, Windows/Linux and consumer type tests.
  • M5: alpha, real product integration, RC and stable.

Compatibility convention

txt
@vevedh/qform-builder-layer   # retained layer distribution
@vevedh/nuxt-form-builder    # new module distribution

The module should be a new envelope around the same core, not a functional rewrite.

QForm Builder — Nuxt 4, Quasar and FormKit layer for dynamic forms.