Optimisez l'architecture de votre blog VitePress avec Vite

- Read in english
Ressources: garabit

Dans l'article précédent, nous avons initié notre projet VitePress et examiné sa structure. Nous avons également commencé à intégrer notre thème personnalisé. Aujourd'hui, nous allons progresser davantage en définissant l'architecture du blog et son design afin de garantir qu'il reste gérable et évolutif au fil du temps.

Le Répertoire src

Pour commencer, nous allons déplacer les fichiers source de notre projet dans un répertoire src désigné. Cette approche évite de surcharger le répertoire racine avec des fichiers source et empêche l'inclusion de fichiers indésirables comme README.md dans les pages de notre projet.

Que constituent nos fichiers source ? Plus précisément, le thème est-il un fichier source ? La réponse est non. Comme nous développons un site centré sur le contenu, seuls les fichiers markdown et publics sont considérés comme des fichiers source. Tout ce qui concerne le thème fait partie de la configuration de VitePress et résidera dans le répertoire .vitepress.

Pour modifier le répertoire source, nous devons mettre à jour l'option srcDir dans le fichier config.mts situé dans le répertoire .vitepress. Voici le contenu révisé du fichier config.mts :

ts
import { defineConfig } from 'vitepress'

export default defineConfig({
  srcDir: 'src',

  // ...
})

Cette option informe VitePress où sont stockées nos pages markdown par rapport à la racine du projet. Dans notre cas, la racine du projet est la racine du dépôt.

Le répertoire src comprendra également un répertoire public, où seront stockés des actifs statiques comme des images, des polices et d'autres fichiers non modifiés.

Note

Le répertoire public est associé à la racine du site et n'est pas traité par VitePress. Par conséquent, les fichiers dans le répertoire public seront copiés à la racine du site généré sans aucune transformation.

txt
.
├── .vitepress
│   └── config.mts
├── src
│   ├── public
│   └── index.md
└── package.json

Cette structure est significativement plus propre et mieux organisée. Si nous souhaitons rédiger un article de blog, nous ouvrons simplement le répertoire src, et si nous souhaitons mettre à jour notre thème, nous accédons au répertoire .vitepress.

Le Répertoire .vitepress

Concernant le répertoire .vitepress, nous pouvons créer des sous-répertoires pour aider à structurer notre thème, en maintenant la simplicité d'un projet standard Vite + Vue.

Sous .vitepress/theme, nous établirons les répertoires suivants :

  • components : Contient tous les composants Vue utilisés dans le thème.
  • composables : Contient toutes les fonctions composables Vue utilisées dans le thème.
  • pages : Contient tous les composants Vue employés comme pages dans le composant Layout.vue.
  • styles : Contient tous les fichiers CSS appliqués dans le thème.
  • types : Contient tous les types TypeScript utilisés dans le thème.
  • utils : Contient toutes les fonctions utilitaires utilisées dans le thème.
txt
.
├── .vitepress
│   ├── theme
│   │   ├── components
│   │   ├── composables
│   │   ├── pages
│   │   ├── styles
│   │   ├── types
│   │   ├── utils
│   │   ├── index.ts
│   │   └── Layout.vue
│   └── config.mts
├── src
│   ├── public
│   └── index.md
└── package.json

Cette structure ressemble de près à ce à quoi de nombreux développeurs Vue sont habitués, ce qui est avantageux ! Cela supprime la nécessité d'apprendre une nouvelle structure de projet tout en utilisant VitePress. Si vous êtes familiers avec la structuration d'un projet Vue, vous savez déjà comment structurer un projet VitePress. 👌

Le Répertoire pages

Je reconnais que cela peut sembler déroutant. J'ai mentionné plus tôt que VitePress dispose d'un routeur minimal, et que les routes sont générées à partir de la structure du répertoire src. Alors, que signifient ces pages ?

VitePress suit la convention d'utiliser Layout.vue comme point d'entrée de l'application Vue. Dans ce scénario, avoir un répertoire layouts impliquerait qu'un layout utilise un layout, ce qui me semble étrange.

Renommer Layout.vue en App.vue pourrait être une option, mais je la trouve moins séduisante. Je préfère conserver le nom Layout.vue car c'est une convention de VitePress. De plus, comme VitePress n'a pas de routeur, nous ne pouvons pas utiliser un composant RouterView pour rendre le layout et la page. Cela implique que Layout.vue et App.vue remplissent des fonctions différentes.

En fin de compte, établir un répertoire pages semblait la décision la plus logique. Dans ce répertoire, nous incluons des composants Vue tels que NotFound.vue, Blog/BlogIndex.vue, Blog/BlogPost.vue, etc., formant un système de routage basé sur les fichiers.

Note

Le répertoire pages est mon approche pour organiser le projet. Ce n'est pas imposé par VitePress. Tous les fichiers .vue pourraient résider dans le répertoire components ou layouts. N'hésitez pas à personnaliser la structure selon vos préférences.

En conclusion, nous utiliserons Layout.vue pour router nos fichiers Markdown vers le composant de page approprié. Pour ce faire, nous utiliserons la clé layout dans le frontmatter de nos fichiers Markdown. Cette clé facilite l'importation du composant de page correct dans le fichier Layout.vue.

Considérez le fichier Markdown suivant :

md
---
layout: blog-post
---

# Bonjour, le Monde !

Dans le fichier Layout.vue, nous pouvons rendre le composant Blog/BlogPost.vue comme suit :

vue
<script lang="ts" setup>
import { useData } from 'vitepress'

const { frontmatter } = useData() // C'est le frontmatter du fichier Markdown actuel
</script>

<template>
  <BlogPost v-if="frontmatter.layout === 'blog-post'" />

  <Content v-else /> // Rendre simplement le contenu du fichier Markdown
</template>

Cette approche permet un système de routage dynamique sans routeur réel. Dans le même temps, Layout.vue reste le point d'entrée de l'application Vue.

Note

Si quelque chose n'est pas clair, ne vous inquiétez pas. Nous appliquerons cela dans les prochains articles lors de l'introduction des articles de blog et de la liste de blog.

Configuration de Unplugin Vue Components

Dans le précédent extrait de code, nous avons utilisé le composant BlogPost sans l'importer. Cette magie devient possible grâce à Unplugin Vue Components.

Note

Cette étape est entièrement optionnelle, et vous pouvez l'omettre si vous le souhaitez. Pour moi, c'est un excellent moyen de maintenir un code propre, et en tant qu'utilisateur de Nuxt, j'ai une légère préférence pour cette méthode. 😅

En coulisses, ce plugin pour Vite scanne nos fichiers à la recherche de composants Vue lorsqu'ils passent par le pipeline de Vite. Lorsqu'il détecte un composant, il ajoute la déclaration d'importation pertinente en haut du fichier. Cela s'aligne sur la philosophie à la demande de Vite, garantissant de petites tailles de bundle et permettant le découpage de composants là où ils sont utilisés.

Ce plugin s'intègre parfaitement à TypeScript, veillant à ce que nous ne compromettons ni le contrôle de type ni l'auto-complétion dans notre IDE.

Installation des Dépendances Nécessaires

Tout d'abord, nous devons installer TypeScript et les types Node. Cela est essentiel car le plugin génère un fichier de déclaration contenant les types de nos composants.

bash
pnpm add -D typescript @types/node vue

Note

L'installation de vue est requise lors de l'utilisation de pnpm en raison de son mécanisme de levée. Consultez l'option shamefully-hoist pour plus d'explications.

Ensuite, un fichier tsconfig.json doit être créé à la racine du projet pour orienter le comportement de TypeScript.

json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true
  },
  "include": ["./.vitepress/**/*"]
}

Les @types/node sont automatiquement intégrés par TypeScript. Nous incluons tous les fichiers dans le répertoire .vitepress où se trouvent nos composants TypeScript et Vue. Ce modèle importe également les fichiers .d.ts, ce qui est crucial pour le plugin.

Nous devons également ajouter "type": "module" dans le fichier package.json et modifier les extensions de postcss.config.js et tailwind.config.js en .cjs.

Configuration

Finaliser la configuration du plugin est simple :

  1. Indiquons au plugin le répertoire où se trouvent nos composants Vue.
  2. Définissons au plugin les conventions de nommage pour nos composants Vue.
  3. Spécifions au plugin l'emplacement pour créer le fichier de déclaration.

Étant donné que le fichier config.mts réside dans le répertoire .vitepress, il y a plus de configuration que dans un projet Vite standard où la configuration vite.config.ts est située à la racine du projet.

ts
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import Components from 'unplugin-vue-components/vite'
import { defineConfig } from 'vitepress'

const currentDir = dirname(fileURLToPath(import.meta.url))

const componentsDir = resolve(currentDir, 'theme', 'components')
const pagesDir = resolve(currentDir, 'theme', 'pages')

export default defineConfig({
  // ...

  vite: {
    plugins: [
      Components({
        dirs: [
          componentsDir,
          pagesDir,
        ],
        include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
        dts: resolve(
          currentDir,
          'components.d.ts'
        ),
      }),
    ],
  },
})

L'option dirs dirige le plugin vers l'emplacement de nos composants. Dans notre contexte, nous avons deux répertoires : theme/pages et theme/components, relatifs au fichier config.mts. Cette distinction est importante car le processus commence à la racine du projet, mais la configuration se trouve dans le répertoire .vitepress. Cela évite l'utilisation de ../../ dans les chemins.

L'option include spécifie les extensions de fichier qui doivent être scannées pour ajouter des déclarations d'importation. Nous incluons les fichiers .vue et .md car nous pourrions avoir des composants Vue dans des fichiers Markdown sans avoir besoin d'importations explicites, ce qui est une fonction fantastique.

L'option dts guide le plugin sur l'endroit où créer le fichier de déclaration. Nous souhaitons créer le fichier components.d.ts à la racine du répertoire .vitepress. Ce fichier de déclaration aide notre IDE et TypeScript à fournir une auto-complétion et un contrôle de type, même lorsque les composants ne sont pas explicitement importés.

Lorsque nous lançons notre serveur de développement, nous devrions vérifier le bon fonctionnement du plugin en examinant le fichier components.d.ts. Au départ, ce fichier peut ne pas comporter de composants car nous n'en avons pas encore. Créer un composant temporaire permettra d'observer les changements et nécessitera un redémarrage du serveur de développement. Utilisons r pour redémarrer le serveur.

Note

La configuration actuelle ne fonctionnera pas comme décrit, et c'est normal. Continuez à lire pour comprendre pourquoi et voir la configuration finale.

ts
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}

/* prettier-ignore */
declare module 'vue' {
  export interface GlobalComponents {
  }
}

Ce fichier est généré par le plugin, ne doit pas être modifié manuellement et doit être ajouté au fichier .gitignore.

txt
node_modules
cache
components.d.ts

Cependant, la configuration actuelle ne fonctionne pas en raison de limitations provenant de Vite. En arrière-plan, le plugin est connecté au système de surveillance des fichiers de Vite, ce qui signifie qu'il ne répond qu'aux fichiers et répertoires surveillés par Vite. Par défaut, Vite ne surveille que le répertoire root spécifié par le srcDir du fichier config.mts. Par conséquent, les fichiers et répertoires dans le répertoire .vitepress ne sont pas détectés par Vite et le plugin.

Pour résoudre ce problème, nous devons indiquer à Vite de surveiller le répertoire où se trouvent nos composants et pages Vue. Cela se fait en concevant un plugin compact pour mettre à jour la configuration de surveillance de Vite :

ts
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitepress'

const currentDir = dirname(fileURLToPath(import.meta.url))

const componentsDir = resolve(currentDir, 'theme', 'components')
const pagesDir = resolve(currentDir, 'theme', 'pages')

export default defineConfig({
  srcDir: 'src',

  vite: {
    plugins: [
      {
        name: 'watcher',
        configureServer(server) {
          server.watcher.add([componentsDir, pagesDir])
        },
      },
      // ...
    ],
  },
})

Avec cet ajustement, tout devrait fonctionner comme prévu. Le plugin génère le fichier de déclaration, fournissant l'auto-complétion et le contrôle de type dans notre IDE lorsqu'un composant est créé sans avoir besoin de redémarrer le serveur de développement.

Tout à la Demande

Cette philosophie à la demande peut-elle s'étendre encore plus à tout ?

Absolument, et nous allons y parvenir en utilisant le plugin unplugin-auto-import. Ce plugin fonctionne de manière similaire au plugin unplugin-vue-components mais pour les fichiers JavaScript et TypeScript.

Tout d'abord, nous devons installer le plugin :

bash
pnpm add -D unplugin-auto-import

Ensuite, configurons le plugin dans le fichier config.mts :

ts
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import AutoImport from 'unplugin-auto-import/vite'
import { defineConfig } from 'vitepress'

const currentDir = dirname(fileURLToPath(import.meta.url))

// ...

const composablesDir = resolve(currentDir, 'theme', 'composables')
const utilsDir = resolve(currentDir, 'theme', 'utils')

export default defineConfig({
  srcDir: 'src',

  vite: {
    plugins: [
      {
        name: 'watcher',
        configureServer(server) {
          // ...
          server.watcher.add([composablesDir, utilsDir])
        },
      },
      AutoImport({
        imports: ['vue', 'vitepress'],
        dirs: [composablesDir, utilsDir],
        dts: resolve(currentDir, 'auto-imports.d.ts'),
      }),
      // ...
    ],
  },
})

Avec cette configuration, nous atteignons trois objectifs :

  1. Importer automatiquement toutes les fonctions composables et utilitaires depuis vue et vitepress, permettant l'utilisation de ref, computed, useData, etc., sans importations explicites.
  2. Scanner et enregistrer toutes les fonctions composables et utilitaires dans les répertoires theme/composables et theme/utils.
  3. Permettre au système de surveillance de surveiller les répertoires theme/composables et theme/utils, de la même manière que le plugin unplugin-vue-components.

Ce plugin génère un fichier auto-imports.d.ts pour les mêmes raisons que le fichier components.d.ts. Il doit également être ajouté au fichier .gitignore.

txt
node_modules
cache
components.d.ts
auto-imports.d.ts

Scripts

Pour conclure cet article, supprimons le préfixe docs: de nos scripts dans le fichier package.json. Comme nous ne créons pas de documentation, et que VitePress est le seul projet dans le dépôt, nous pouvons éliminer en toute sécurité ce préfixe.

json
{
  "scripts": {
    "dev": "vitepress dev",
    "build": "vitepress build",
    "preview": "vitepress preview"
  }
}

Excellent ! Nous avons maintenant une structure de projet propre et organisée, prête à commencer notre entreprise de blogging. Dans l'article suivant, nous développerons la liste de blog et tirerons parti d'une des fonctionnalités clés de VitePress : le chargeur de données.