Exemple avec un Nuxt Layer
Choisissez cette approche si votre application utilise déjà extends ou si vous voulez récupérer directement la configuration complète fournie par le package.
Configuration Nuxt
ts
import { fileURLToPath } from 'node:url'
const layerRoot = fileURLToPath(new URL('../..', import.meta.url))
const useFilerobotE2eMock = process.env.NFZ_E2E_MOCK_FILEROBOT === '1'
const fastE2e = process.env.NFZ_E2E_FAST_BOOT === '1'
export default defineNuxtConfig({
compatibilityDate: '2026-05-01',
// Les E2E Windows doivent démarrer vite et de façon déterministe.
// Nuxt DevTools est désactivé explicitement : la variable NUXT_DEVTOOLS seule
// ne suffit pas à empêcher son initialisation avec les versions actuelles.
devtools: {
enabled: !fastE2e
},
// @nuxt/icon reste installé comme dépendance requise du produit, mais son
// scan/server bundle n'est pas utile aux scénarios E2E NFZ et ralentit fortement
// le premier démarrage sous Windows.
icon: fastE2e
? {
provider: 'none',
serverBundle: false,
fallbackToApi: false,
clientBundle: {
scan: false,
icons: []
}
}
: {},
// Scénario repository-first : Nuxt charge le layer local par chemin absolu.
// Ce mode évite les erreurs de cache Bun liées à une dépendance file:../.. pendant
// que le package est encore testé depuis son dépôt source.
extends: [layerRoot],
css: ['~/assets/css/main.css'],
alias: {
'#nfz-editor-types': fileURLToPath(new URL('../../app/types/nfz-notion-markdown-editor.ts', import.meta.url)),
...(useFilerobotE2eMock
? {
'filerobot-image-editor': fileURLToPath(new URL('./tests/e2e/mocks/filerobot-image-editor.ts', import.meta.url))
}
: {})
},
vite: {
optimizeDeps: {
exclude: useFilerobotE2eMock ? ['filerobot-image-editor'] : []
},
// Sur le cold start Windows/Bun, la page éditeur charge une large partie du
// layer. La racine Vite Nuxt 4 est déjà `app/`; le chemin est donc relatif à ce root.
// Vite peut prétransformer cette entrée fréquemment utilisée afin
// d'éviter une cascade de transforms au moment du premier page.goto().
server: fastE2e
? {
warmup: {
clientFiles: ['./pages/editor.vue']
}
}
: undefined
},
typescript: {
strict: true,
typeCheck: false
},
app: {
head: {
title: 'Fresh Nuxt 4 - NFZ Notion Markdown Editor',
meta: [
{
name: 'description',
content: 'Scénario de validation fresh Nuxt 4 pour @vevedh/nfz-notion-markdown-editor.'
}
]
}
}
})Page contenant l’éditeur
vue
<script setup lang="ts">
import { computed, ref } from 'vue'
import type { NfzEditorImageEditedEventPayload, NfzEditorSavePayload, NfzToolbarItemId } from '#nfz-editor-types'
const markdown = ref(`# Scénario fresh Nuxt 4
Cette page valide l’intégration du composant dans une application Nuxt 4 neuve.

## Code
\`\`\`javascript
const editor = 'NFZ'
console.log(editor)
\`\`\`
## Table
| Fonction | État |
| --- | --- |
| Auto-import Nuxt Layer | OK |
| Preview markdown-it | OK |
| Tables normalisées | OK |
| UML Mermaid | OK |
## UML
\`\`\`mermaid
classDiagram
class FreshNuxtApp {
+String framework
+String runtime
}
class NfzNotionMarkdownEditor {
+String markdown
+insertUmlDiagram()
+insertTable()
}
FreshNuxtApp --> NfzNotionMarkdownEditor : extends layer
\`\`\`
`)
const toolbar: NfzToolbarItemId[] = [
'paragraph',
'heading-1',
'heading-2',
'bold',
'italic',
'blockquote',
'source-markdown',
'markdown-preview',
'table',
'uml',
'video',
'save'
]
const lastSave = ref<NfzEditorSavePayload | null>(null)
const lastImageEdit = ref<NfzEditorImageEditedEventPayload | null>(null)
const saveSummary = computed(() => {
if (!lastSave.value) {
return 'Aucune sauvegarde déclenchée pour le moment.'
}
return JSON.stringify(
{
markdownLength: lastSave.value.markdown.length,
htmlLength: lastSave.value.html.length,
textLength: lastSave.value.text.length,
containsDataImage: lastSave.value.markdown.includes('data:image/'),
containsCaption: lastSave.value.markdown.includes('Légende E2E'),
containsTypeScriptFence: lastSave.value.markdown.includes('```typescript')
},
null,
2
)
})
const imageEditSummary = computed(() => {
if (!lastImageEdit.value) {
return 'Aucune édition image appliquée pour le moment.'
}
return JSON.stringify(
{
fileName: lastImageEdit.value.edited.fileName,
mimeType: lastImageEdit.value.edited.mimeType,
width: lastImageEdit.value.edited.width,
height: lastImageEdit.value.edited.height,
edited: lastImageEdit.value.attrs.edited === true,
srcPrefix: String(lastImageEdit.value.attrs.src || '').slice(0, 22)
},
null,
2
)
})
function handleSave(payload: NfzEditorSavePayload): void {
lastSave.value = payload
}
function handleImageEdited(payload: NfzEditorImageEditedEventPayload): void {
lastImageEdit.value = payload
}
function replaceMarkdownFromParent(): void {
markdown.value = '# Synchronisation externe\n\nContenu imposé par le parent via v-model.'
}
</script>
<template>
<main class="fresh-shell" data-testid="fresh-editor-page">
<header class="fresh-header">
<div class="fresh-brand">
<span class="fresh-kicker">Scénario testé</span>
<h1 class="fresh-title">
Éditeur dans une app Nuxt 4 neuve
</h1>
<p class="fresh-subtitle">
Ce scénario vérifie que le composant peut être consommé comme package Nuxt Layer,
avec auto-import du composant, import de types public via alias local, preview Markdown, tables et UML Mermaid.
</p>
</div>
<div class="fresh-header__actions">
<button
type="button"
class="fresh-button"
data-testid="fresh-external-replace"
@click="replaceMarkdownFromParent"
>
Remplacer depuis le parent
</button>
<NuxtLink class="fresh-button" to="/">
Retour
</NuxtLink>
</div>
</header>
<section class="fresh-card fresh-editor-card">
<div class="fresh-card__body">
<NfzNotionMarkdownEditor
v-model="markdown"
placeholder="Écris ton Markdown..."
theme="default"
:toolbar="toolbar"
:toolbar-labels="true"
:show-status-bar="true"
:sticky-toolbar="true"
content-max-width="880px"
:enable-bubble-toolbar="true"
:enable-code-block-tools="true"
:enable-markdown-clipboard="true"
:enable-tables="true"
:enable-media="true"
:enable-source-mode="true"
:enable-markdown-preview-feature="true"
:enable-table-toolbar="true"
:enable-image-toolbar="true"
:enable-image-caption="true"
:enable-filerobot-image-editor="true"
:enable-video-toolbar="true"
:enable-uml="true"
mermaid-theme="default"
@save="handleSave"
@image-edited="handleImageEdited"
/>
<pre class="fresh-log" data-testid="fresh-image-edited-summary">{{ imageEditSummary }}</pre>
<pre class="fresh-log" data-testid="fresh-save-summary">{{ saveSummary }}</pre>
</div>
</section>
</main>
</template>Le fichier affiché ici est le même que celui utilisé par le scénario d’intégration du dépôt.