Publish QForm Builder as a Nuxt Layer npm package
This phase prepares QForm Builder as a Nuxt Layer publishable on npm.
The goal is to install the package in a new or existing Nuxt 4 application and activate it with extends.
Why publish as a layer first?
The current source tree already follows the Nuxt layer model:
app/components
app/composables
app/stores
app/types
app/utils
nuxt.config.ts
formkit.config.tsPublishing as a layer is therefore the fastest and least risky route for the Community edition.
Structure selected for phase 1
qform-builder-layer/
├── app/
├── .playground/
├── docs/
├── formkit.config.ts
├── nuxt.config.ts
└── package.jsonDemo pages are kept in .playground so they are not injected into applications consuming the layer.
Layer nuxt.config.ts
The layer must not force application-level decisions such as ssr: false. The host application remains responsible for SSR, modules and runtime configuration.
Publication package.json
The package exposes the root nuxt.config.ts as its main entry:
{
"name": "@vevedh/qform-builder-layer",
"main": "./nuxt.config.ts",
"publishConfig": {
"access": "public"
}
}Peer dependencies
Nuxt, Vue, Quasar, Pinia and FormKit are declared as peer dependencies. This keeps the host application in control of framework versions.
Test before publishing
bun run docs:build
bun run typecheck
bun run pack:dry-runThe typecheck script automatically runs nuxi prepare .playground before vue-tsc. A complete local validation/startup cycle is:
bun run clean && bun i && bun run typecheck && bun devPowerShell 7 uses && so bun dev is not started when type checking fails. Using ; would continue even after a non-zero exit code.
Publish on npm
npm login
bun run npm:publishInstall in a Nuxt 4 application
bun add @vevedh/qform-builder-layerexport default defineNuxtConfig({
extends: ['@vevedh/qform-builder-layer'],
})Minimal example
<script setup lang="ts">
import type { FormBuilderSchema, FormBuilderValues } from '@vevedh/qform-builder-layer/types'
const schema = ref<FormBuilderSchema>([])
const values = ref<FormBuilderValues>({})
</script>
<template>
<ClientOnly>
<FormBuilder
builder-id="consumer-builder"
v-model:schema="schema"
v-model:values="values"
/>
</ClientOnly>
</template>Layer-owned styles
A published layer must resolve its stylesheet from import.meta.url instead of using ~/assets/...:
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({
css: [layerStylesheet],
vite: {
optimizeDeps: {
include: [
'@formkit/core',
'@formkit/i18n',
'@formkit/utils',
'shiki',
],
},
},
})In a playground or consuming application, ~ points to the host application, not to the installed layer.
This variant does not import node:url, so the generated playground tsconfig does not require a direct @types/node dependency. The normalization only removes the leading slash from Windows paths such as /C:/....
Best practice
Run npm pack --dry-run before every publication and verify that generated files, playground files and internal patch logs are not included.
Final Community contract
Community must remain stable, lightweight and documented. Advanced commercial features should be added through a separate Pro package.
Recommended final commands
bun run release:doctor
bun run npm:publish:dry-run
bun run npm:publishFormKit configuration owned by the layer
The layer resolves and binds its own formkit.config.ts through formkit.configFile. This prevents a playground or host auto-detected file from shadowing the Quasar adapter registry. A host application that intentionally overrides this option must compose qformBuilderQuasarPlugin again.
Version 0.1.3 — QBtn verification
Before publishing 0.1.3, also verify the button panel and its tooltip:
bun run quasar-adapters:check
bun run e2e:static:check
bun run e2e:visual:update
bun run e2e:visualThe scenario must confirm that only one Button label field is exposed and that info appears as a nested QTooltip when hovering the QBtn.
Version 0.1.2 and visual tests
Before publishing 0.1.2, also run:
bun run quasar-advanced:check
bun run quasar-advanced:runtime:check
bun run drop-indicator:check
bun run e2e:static:check
bun run e2e:visualPlaywright screenshots belong to the development repository and are not published in the runtime package. The @vevedh/qform-builder-layer/quasar-options subpath is included in the npm tarball.
Version 0.1.5 — shared QTooltip verification
Before publishing, verify the shared contract and the absence of QBtn duplicates:
bun run tooltip:check
bun run tooltip:runtime:check
bun run quasar-adapters:check
bun run e2e:static:check
bun run e2e:visualIn /quasar-advanced, switch to preview mode and hover QEditor, QSelect, QRating, QInput and the static paragraph. Each info property must display exactly one QTooltip. All four Community button variants must keep buttonLabel and their QTooltip as a direct child of QBtn.
Version 0.1.6 — reliable visual scenarios
Static checks do not replace browser execution. Playwright scenarios now use the following stable markers:
<div data-qform-preview-mode="previewing" aria-label="Preview"></div>
<main data-qform-e2e-page="qtooltip-audit"></main>QTab exposes the ARIA tab role and must not be located as a button. Tooltip assertions must target the teleported Quasar overlay:
const tooltip = page.locator('.q-tooltip').filter({ hasText: expectedText }).last()
await expect(tooltip).toBeVisible()Generate reference screenshots and then verify them with:
bun run e2e:visual:update
bun run e2e:visualThe E2E helper also waits for the cold compilation of /quasar-advanced and /qtooltip-audit, with one controlled retry when Vite performs a transient optimization reload.
Version 0.1.7 — promoted FormKit properties
FormKit exposes properties registered through node.define({ props: [...] }) directly on the component context. A Quasar adapter must therefore not read context.attrs exclusively.
const attrs = resolveQFormBuilderFormKitAttrs(context, [
'buttonLabel',
'info',
'description',
])Before publishing, verify that all four buttons on /quasar-advanced have a visible label, a data-qform-field-name marker and exactly one QTooltip. Also verify every dedicated structure tooltip on /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 — light/dark contract
Release 0.1.8 adds two mandatory gates:
bun run dark-mode:check
bun run dark-mode:runtime:checkThe release build must also run the /dark-mode-audit Playwright scenarios. Switching Quasar appearance must not mutate the serialized theme; the dark palette is derived for rendering only.
Version 0.1.9 — template registry
Release 0.1.9 adds the public ./templates subpath and two mandatory gates:
bun run templates:check
bun run templates:runtime:checkThe runtime check must cover key-based merging, data sanitization, prototype-pollution key rejection, name collisions, condition and cross-field validation rewriting, initial values, stepper conflicts and active-theme preservation.
Before publishing, open /templates, verify search, append, confirmed replacement and the Quasar tooltip on the unavailable append action, then run:
bun run typecheck
bun run runtime:build:check
bun run docs:build
bun run e2e:visual
npm pack --dry-runThe tarball must include app/templates/** and exclude .playground, tests, patch-memory, AGENTS.md and PROMPT_CONTEXT.md.
Release 0.1.16 — accessibility
The tarball must include app/accessibility/** and expose @vevedh/qform-builder-layer/accessibility. Before publishing:
bun install
bun run accessibility:check
bun run release:browser:check
npm pack --dry-runThe browser audit never updates visual baselines. Any new baseline must be generated separately, reviewed, and then committed.
0.1.17 release — CI matrix and verified artifact
Before a release candidate, the repository must now produce the same contract on Windows and Linux:
bun run ci:quality
bun run e2e:ci:functional
bun run release:browser:check
bun run pack:artifactThe package job retains the .tgz file and package-manifest.json as GitHub Actions artifacts. The manifest contains package identity, size, npm integrity and the tarball SHA-256 digest. pack:verify rejects fixtures, workflows, tests, lockfiles and internal journals from the published package.
The tests/host-app build complements the playground by validating real SSR consumption of the layer and its public subpaths before publishing.