Skip to content

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.

![Logo de démonstration](/demo-editor-image.png)

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

NFZ Notion Markdown Editor