Créer une Bibliothèque de Composants Vue.js Simplement

- Read in english

TL;DR: vue-library Continuez à lire pour comprendre le processus derrière cela et les raisons de mes choix.

Une bibliothèque de composants est une collection de morceaux réutilisables qui peuvent être utilisés dans divers projets. Elle facilite le partage de ressources entre différents projets et équipes. Ces composants peuvent être basiques et génériques comme des boutons, des champs de saisie et des modales, ou des modules plus spécifiques au métier. En fin de compte, c'est une méthode pour partager du code entre les projets, une compétence cruciale qui peut faire économiser beaucoup de temps.

Cependant, construire une bibliothèque de composants avec Vue.js est plus difficile qu'il n'y paraît en raison des Single File Components (SFC). Dans un projet TypeScript standard, vous transpilez généralement en JavaScript et regroupez vos fichiers avec un outil comme tsup ou Vite.

Note

N'oubliez pas de livrer le code du paquet aussi natif que possible et de laisser les outils de l'utilisateur gérer la transpilation et l'optimisation. C'est un principe que je garde toujours à l'esprit lorsque je crée des bibliothèques, ce qui simplifie considérablement le processus. Chaque cas est unique, mais c'est une bonne ligne directrice à suivre.

L'utilisation des SFC de Vue.js complique ce processus. Examinons pourquoi en explorant les défis que vous rencontrerez lors de la construction d'une bibliothèque de composants Vue.js.

Considérons le composant suivant :

vue
<script lang="ts" setup>
import { useUser } from '../composables'

const { user } = useUser()
</script>

<template>
  <div v-if="user">
    {{ user.name }}
  </div>
</template>

Le composable est le suivant :

ts
import { ref } from 'vue'

export function useUser() {
  const user = ref({ name: 'John Doe' })

  return {
    user
  }
}

Le composant utilise un composable importé d'un autre fichier. Cela peut sembler trivial, mais cela pose un problème significatif.

Tout cela suit l'architecture ci-dessous :

src/
  components/
    User.vue
  composables/
    useUser.ts
  index.ts

Le fichier index.ts sert de point d'entrée de la bibliothèque et ressemble à ceci :

ts
import User from './components/User.vue'

export { User }

export { useUser } from './composables/useUser'

De plus, le package.json inclut quelques exports :

json
{
  "type": "module",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs"
    }
  },
  "main": "dist/index.mjs",
  "types": "dist/index.d.ts"
}

Similaire à un projet TypeScript standard

Note

Un bundle est un seul fichier encapsulant tout le code de votre projet. Il optimise le chargement du projet en minimisant le nombre de fichiers à charger. Ce bundle n'est pas transpile (sauf TypeScript vers JavaScript) ni minifié lors de la création d'un package npm.

Maintenant, si nous tentons de regrouper ce projet, notre dossier dist apparaîtra comme ceci :

dist/
  index.cjs

En inspectant index.cjs, vous trouverez une surprise : le composable useUser est intégré, mais le composant est complètement ignoré. S'il fonctionne, l'outil pourrait déclencher une erreur du type No loader is configured for ".vue" files. Essentiellement, des outils comme esbuild ou tsc ne savent pas comment gérer les fichiers Vue. C'est logique, car les fichiers Vue ne sont pas des fichiers JavaScript.

Le problème devient évident. Nous avons besoin d'une configuration pour gérer les fichiers Vue. Jetons un coup d'œil rapide sur Internet pour quelque chose comme vue loader, car nous utilisons des fichiers Vue et devons les transpiler en JavaScript.

Le premier résultat est Vue Loader, qui est un loader Webpack. Non. Je refuse d'utiliser Webpack. Vite est maintenant un standard, et il n'y a aucune justification pour employer Webpack dans ce contexte.

Mais avant de plonger plus loin, rappelons-nous notre mantra : livrer le code du paquet aussi natif que possible et keep it simple, stupid! La transpilation des fichiers SFC Vue avec Webpack semble contraire à ce principe.

Ignorer les fichiers Vue

Pour livrer des fichiers .vue sans les transpiler, nous demandons à notre outil de les ignorer. Cela est réalisé à l'aide d'un outil appelé unbuild provenant de l'écosystème UnJS.

Unbuild est un outil simple mais hautement configurable car il est construit sur Rollup.

Pour notre petit projet, nous pouvons l'expérimenter directement :

sh
npx unbuild

Malheureusement, cela échoue pour les mêmes raisons que précédemment :

txt
src/components/ShowGitHubUser.vue (1:0): Expression expected (Note that you need plugins to import files that are not JavaScript)

Mais unbuild a le potentiel pour bien plus.

Pour résoudre ce problème, essayons la solution évidente. Dans le fichier index.ts, supprimons l'exportation du composant et exportons uniquement les fichiers TypeScript :

ts
export { useUser } from './composables/useUser'

Maintenant, la commande npx unbuild fonctionne correctement.

sh
 npx unbuild
 Automatically detected entries: src/index [esm] [dts]
 Building vue-library
 Cleaning dist directory: ./dist
 Build succeeded for vue-library
  dist/index.mjs (total size: 139 B, chunk size: 139 B, exports: useUser)

Σ Total dist size (byte size): 531 B

C'est un bon début, mais nos composants restent dans le dossier src et peu importe combien vous cherchez ardemment, ils sont absents du dossier dist.

Copier les fichiers Vue

Maintenant que les fichiers .ts sont traités correctement, nous pouvons tenter de copier les fichiers .vue dans le dossier dist avec une simple commande cp.

sh
cp -r src/components/ dist/components/

Ensuite, nous ajoutons un champ exports dans le package.json pour informer l'utilisateur où localiser les composants :

json
{
  "type": "module",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs"
    },
    "./components/*": {
      "import": "./dist/components/*.vue"
    }
  },
  "main": "dist/index.mjs",
  "types": "dist/index.d.ts"
}

Ça a l'air prometteur, n'est-ce pas ? Mais ce n'est pas le cas. 😔

Le dilemme du chemin d'importation

Dans notre composant Vue, le composable useUser est importé comme ceci :

vue
<script lang="ts" setup>
import { useUser } from '../composables'
</script>

Pendant ce temps, notre dossier dist est structuré comme suit :

dist/
  components/
    User.vue
  index.d.mts
  index.d.ts
  index.mjs

Pouvez-vous identifier le problème ? 👀

Le composant est inutilisable et produira une erreur du type Cannot find module '../composables' or its corresponding type declarations, car le chemin mène nulle part. Nous avons regroupé tous nos fichiers TypeScript dans un seul fichier index.mjs, perdant complètement la structure du projet.

Regrouper ou ne pas regrouper ?

À partir de là, nous avons deux choix :

  1. Regrouper les fichiers Vue en utilisant Vite, des plugins et une configuration extensive.
  2. Conserver la structure de répertoires et utiliser un outil comme mkdist pour une transpilation fichier à fichier (construction sans bundle) sur les fichiers TypeScript.

La décision dépendra largement de vos besoins spécifiques, mais je suis convaincu que la simplicité est la meilleure approche. Donc, explorons la deuxième option.

L'objectif est de transformer les fichiers TypeScript en fichiers JavaScript, tout en préservant la structure d'origine du dossier src dans le dossier dist et en ignorant les fichiers Vue.

Note

En utilisant un bloc <script setup lang="ts">, mkdist ignorera les fichiers Vue pour permettre au compilateur Vue de générer des props d'exécution. Sinon, il générera un fichier .vue.d.ts pour offrir des types pour le composant Vue. Rappelez-vous que le @vitejs/plugin-vue et Vite comprennent intrinsèquement les blocs de script TypeScript. Pour plus de détails, consultez issue mkdist#14.

Configurer Unbuild

Oui, nous allons configurer unbuild car il intègre mkdist, ce qui le rend aggréable à utiliser.

Commencez par créer un fichier build.config.ts à la racine du projet. Unbuild lira ce fichier de configuration pour comprendre comment traiter le projet. Il suppose des valeurs par défaut et infère de nombreux aspects à partir du package.json, mais nous devons lui dire d'utiliser mkdist.

ts
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  entries: ['./src/'],
  declaration: true,
})

C'est un fichier de configuration très simple. Et je l'adore. La clé entries informe unbuild du fichier initial à commencer à transpiler. Cependant, ./src/ est un répertoire, indiqué par le / à la fin. Avec cet indice, unbuild contourne le regroupement par défaut rollup en faveur de mkdist. La clé declaration incite unbuild à créer des fichiers de déclaration TypeScript (.d.ts).

Nous pouvons également supprimer le champ exports dans le package.json puisque nous ne regrouperons plus les fichiers et restaurer l'exportation du composant dans le fichier index.ts.

Relaçons la commande npx unbuild et magie :

sh
 npx unbuild
 Building vue-library
 Cleaning dist directory: ./dist
 Build succeeded for vue-library
  dist (total size: 600 B)
  └─ dist/index.d.ts (108 B)
  └─ dist/index.mjs (112 B)
  └─ dist/composables/useUser.d.ts (79 B)
  └─ dist/composables/useUser.mjs (124 B)
  └─ dist/components/User.vue (177 B)
Σ Total dist size (byte size): 600 B

Maintenant, le dossier dist apparaît comme ceci :

dist/
  components/
    User.vue
  composables/
    useUser.d.ts
    useUser.mjs
  index.d.ts
  index.mjs

Cela reflète la structure du dossier src, et le composant User.vue est maintenant utilisable dans le dossier dist. Le chemin d'importation relatif reste intact, et le composant peut être utilisé dans n'importe quel projet Vue. 🥳

Développement local

De nombreux tutoriels s'arrêtent ici, suggérant que vous êtes prêt à publier votre package sur npm. Mais comment pouvez-vous créer une bibliothèque complexe si vous ne pouvez pas voir le résultat de votre travail ? Si vous ne pouvez pas la tester tout en la construisant ?

Vous ne pouvez pas.

Examinons comment utiliser la bibliothèque dans un projet local, au sein du même dépôt pour plus de simplicité.

Je vais expliquer la méthode la plus simple en utilisant un pnpm workspace.

Commencez par créer un fichier pnpm-workspace.yaml à la racine du projet :

yaml
packages:
  - .
  - playground

Ensuite, créez un nouveau projet Vite dans un dossier playground :

sh
npx create-vite playground --template vue-ts

Puisque nous utilisons un pnpm workspace, installez les dépendances à partir de la racine du projet :

sh
pnpm install

Note

Cette étape n'est pas obligatoire. Vous pouvez créer un nouveau projet Vite et installer les dépendances à l'intérieur. Pnpm simplifie cela en installant les dépendances pour tous les espaces de travail à la racine. Une simple commande pnpm install installera les dépendances de tous les espaces de travail.

Maintenant, nous pouvons utiliser cette bibliothèque playground dans un projet "réel" pour tester notre bibliothèque.

Par exemple, ouvrez le fichier src/App.vue et importez le composant User :

vue
<script setup lang="ts">
import { User } from '../../src'
</script>

<template>
  <User />
</template>

Et exécutez le projet :

sh
cd playground && pnpm dev

Vous verrez le composant User s'afficher dans le navigateur. 🎉 Si simple, mais si puissant. J'utilise cette méthode pour presque toutes les bibliothèques que je construis, et cela fonctionne parfaitement.

Publication sur npm

Cette partie est simple une fois que vous avez compris le flux de travail.

Commencez par nommer votre bibliothèque. J'utiliserai @barbapapazes/vue-library pour cet exemple.

Ensuite, créez un compte sur npm et connectez-vous en utilisant la commande npm login.

Puis, installez changelogen pour générer un changelog, mettre à jour le numéro de version selon la gestion sémantique des versions et les conventional commits, et pré-remplir la version sur GitHub.

sh
pnpm i -D changelogen

Ensuite, ajoutez deux scripts à votre package.json :

json
{
  "scripts": {
    "prepack": "unbuild",
    "release": "changelogen --release && npm publish --access public && git push --follow-tags"
  }
}

Le script prepack exécute la commande unbuild avant de publier le package sur npm. Le script release génère un changelog, publie le package sur npm, et pousse les tags sur GitHub.

Note

Votre projet doit être sur GitHub pour que l'option --release fonctionne. Sinon, omettez-là et créez la version manuellement sur votre dépôt.

Maintenant, publiez votre package sur npm en utilisant la commande suivante :

sh
npm run release

Et c'est tout ! Vous venez de publier votre première bibliothèque de composants Vue.js sur npm. 🚀

Tout est prêt

Nous avons terminé pour aujourd'hui. Nous avons couvert de nombreux sujets :

  • Comment construire une bibliothèque de composants Vue.js avec TypeScript.
  • Comment gérer les fichiers Vue dans un projet TypeScript.
  • Comment utiliser unbuild pour transpiler les fichiers TypeScript sans regrouper les fichiers Vue.
  • Comment tirer parti de mkdist pour maintenir la structure du projet.
  • Comment utiliser un pnpm workspace pour tester la bibliothèque dans un projet local.
  • Comment publier la bibliothèque sur npm.

Pour un exemple plus détaillé et complexe, consultez GitHub : vue-library mais tout ce qui a été expliqué ici provient de mon expérience dans le monde réel.

J'espère que vous avez apprécié ce tutoriel et qu'il vous aidera à construire votre propre bibliothèque de composants Vue.js. Si vous avez des questions, n'hésitez pas à me contacter sur X (Twitter).

Soutenir mon travail
Suivez-moi sur