![CDATA[
Contexte et objectif
Les modèles de langage spécialisés pour le code permettent désormais de produire un squelette d’application complet à partir d’une simple description textuelle. Cette approche est particulièrement utile pour lancer rapidement un projet frontend sans passer par les phases répétitives d’initialisation et de configuration. L’exemple suivant montre comment obtenir, via l’interface WebUI de Gemma 3 12B IT, une structure fonctionnelle pour une application de gestion de tâches basée sur Vue 3, Pinia, TypeScript et Vite.
Formulation du prompt
La qualité du résultat dépend fortement de la précision de l’instruction. Une description vague comme "Créer une Todo app" génère un rendu peu exploitable. Une formulation technique et structurée donne un résultat cohérent :
Générer un projet Vue 3 + Pinia + TypeScript + Vite pour une application de gestion de tâches. Le livrable doit inclure :
- une arborescence complète et modulaire ;
- les fichiers de configuration (vite.config.ts, tsconfig.json, vitest.config.ts) ;
- les composants AddTask, TaskItem, TaskFilter et TaskBoard ;
- un store Pinia utilisant la Composition API ;
- une configuration de routage minimale ;
- les styles scoped et une structure de tests unitaires.
Le résultat doit être présenté sous forme d’arbre de fichiers Markdown, avec une description des fichiers critiques.
Arborescence du projet généré
Le modèle propose une architecture proche des conventions actuelles de l’écosystème Vue :
taskflow/
├─ public/
├─ src/
│ ├─ assets/
│ ├─ components/
│ │ ├─ TaskForm.vue
│ │ ├─ TaskItem.vue
│ │ └─ TaskFilter.vue
│ ├─ composables/
│ │ └─ useStorage.ts
│ ├─ router/
│ │ └─ index.ts
│ ├─ stores/
│ │ └─ taskStore.ts
│ ├─ types/
│ │ └─ task.ts
│ ├─ views/
│ │ ├─ BoardView.vue
│ │ └─ SettingsView.vue
│ ├─ App.vue
│ └─ main.ts
├─ tests/
│ └─ unit/
├─ package.json
├─ tsconfig.json
├─ vite.config.ts
├─ vitest.config.ts
└─ README.md
Implémentation du store Pinia
Le store géré par Pinia adopte ici un style fonctionnel (setup store), avec des types explicites et une séparation claire entre état, getters et actions :
// src/stores/taskStore.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export interface Task {
id: number
content: string
isDone: boolean
createdAt: Date
}
export const useTaskStore = defineStore('task', () => {
const tasks = ref<Task[]>([
{ id: 1, content: 'Explorer Vue 3', isDone: true, createdAt: new Date() },
{ id: 2, content: 'Maîtriser Pinia', isDone: false, createdAt: new Date() }
])
const selectedFilter = ref<'all' | 'active' | 'done'>('all')
const visibleTasks = computed(() => {
if (selectedFilter.value === 'active') {
return tasks.value.filter(t => !t.isDone)
}
if (selectedFilter.value === 'done') {
return tasks.value.filter(t => t.isDone)
}
return tasks.value
})
const remainingCount = computed(() => tasks.value.filter(t => !t.isDone).length)
const doneCount = computed(() => tasks.value.filter(t => t.isDone).length)
function addTask(content: string) {
const value = content.trim()
if (!value) return
tasks.value.push({
id: Date.now(),
content: value,
isDone: false,
createdAt: new Date()
})
}
function toggleTask(id: number) {
const task = tasks.value.find(t => t.id === id)
if (task) task.isDone = !task.isDone
}
function deleteTask(id: number) {
const index = tasks.value.findIndex(t => t.id === id)
if (index >= 0) tasks.value.splice(index, 1)
}
function setFilter(filter: 'all' | 'active' | 'done') {
selectedFilter.value = filter
}
function archiveDone() {
tasks.value = tasks.value.filter(t => !t.isDone)
}
return {
tasks,
selectedFilter,
visibleTasks,
remainingCount,
doneCount,
addTask,
toggleTask,
deleteTask,
setFilter,
archiveDone
}
})
Les points forts de cette implémentation sont : l’utilisation de la syntaxe setup, les types TypeScript stricts, l’exposition sélective des propriétés et la logique de filtrage déléguée à un getter calculé.
Composant de ligne de tâche
Le composant TaskItem.vue illustre une composition moderne avec script setup, des props typées et des styles scoped :
<!-- src/components/TaskItem.vue -->
<template>
<li :class="{ done: task.isDone }" class="task-row">
<label class="task-label">
<input
type="checkbox"
:checked="task.isDone"
@change="handleToggle"
class="task-check"
/>
<span class="task-content">{{ task.content }}</span>
</label>
<time :datetime="task.createdAt.toISOString()" class="task-date">
{{ formatDate(task.createdAt) }}
</time>
<button type="button" class="task-remove" @click="handleRemove">×</button>
</li>
</template>
<script setup lang="ts">
import { useTaskStore } from '@/stores/taskStore'
import type { Task } from '@/stores/taskStore'
const props = defineProps<{ task: Task }>()
const store = useTaskStore()
function handleToggle() {
store.toggleTask(props.task.id)
}
function handleRemove() {
store.deleteTask(props.task.id)
}
function formatDate(date: Date) {
return new Intl.DateTimeFormat('fr-FR', { day: 'numeric', month: 'short' }).format(date)
}
</script>
<style scoped>
.task-row {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem 1rem;
border-bottom: 1px solid #e5e5e5;
}
.task-row.done .task-content {
text-decoration: line-through;
color: #888;
}
.task-label {
display: flex;
align-items: center;
gap: 0.75rem;
flex: 1;
}
.task-check {
width: 1.25rem;
height: 1.25rem;
cursor: pointer;
}
.task-content {
font-size: 1rem;
}
.task-date {
font-size: 0.75rem;
color: #666;
}
.task-remove {
background: transparent;
border: none;
color: #e74c3c;
font-size: 1.5rem;
cursor: pointer;
opacity: 0.6;
transition: opacity 0.2s;
}
.task-remove:hover {
opacity: 1;
}
</style>
Configuration de l’outil de build
Le fichier vite.config.ts définit un alias @ pointant vers le dossier src, un port de développement et une stratégie de découpage manuel des chunks :
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
port: 5173,
open: false
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
framework: ['vue', 'pinia', 'vue-router']
}
}
}
}
})
Le tsconfig.json active le mode strict et la résolution de modules via bundler, ce qui garantit une cohérence avec le fonctionnement de Vite :
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.vue"]
}
Analyse de l’architecture
La structure générée respecte plusieurs principes fondamentaux :
- Separation of concerns : l’état métier reste dans le store, la logique d’interface dans les composants, la réutilisabilité dans les composables.
- Type safety : l’interface
Taskest utilisée à la fois dans le store et dans les props des composants. - Composabilité : les fonctionnalités peuvent être étendues via des modules Pinia supplémentaires ou des vues dédiées.
Paramétrage du modèle
Pour ce type de tâche, les paramètres suivants donnent un bon équilibre entre stabilité et flexibilité :
- Temperature : 0.3 à 0.5, afin de privilégier un code conventionné.
- Top P : 0.85 pour conserver une certaine diversité sans dévier des bonnes pratiques.
- Max Tokens : 3000 à 4000, car la description d’une arborescence complète demande de l’espace.
Le résultat brut doit ensuite être adapté : ajout de règles de validation, ajustement des styles, complétion des tests et nettoyage des dépendances superflues.
]]>