Construire un premier composant avec Vue.js et Tailwind CSS
Avec notre bibliothèque de composants Vue.js configurée, il est temps de construire notre premier composant : un bouton simple. Cet exercice nous aidera à comprendre comment structurer nos composants.
Nous allons utiliser Tailwind CSS et Tailwind Variants pour le style. Tailwind Variants aide à organiser les classes Tailwind CSS de manière structurée pour créer et combiner des variantes.
Configuration du Package
Naviguez vers packages/huchet-vue
et installez les dépendances nécessaires :
cd packages/huchet-vue && pnpm add -D vue vite
Ensuite, créez un dossier src
avec le composant Button/Button.vue
:
mkdir -p src/Button && touch src/Button/Button.vue
Ouvrez le fichier Button.vue
et commençons à écrire notre composant.
Écriture du Composant Bouton
Pour notre exemple, nous allons créer un bouton avec deux variantes : solide et contour. La variante solide aura une couleur de fond, tandis que la variante contour inclura une bordure.
Pour assurer l'absence de conflit de classes et simplifier le refactoring, la maintenabilité, la lisibilité et la structure, nous utiliserons Tailwind Variants. Nous allons organiser nos classes dans un objet, et Tailwind Variants appliquera les classes appropriées en fonction des props.
Ajoutez une balise script
en haut du fichier Button.vue
:
<script lang="ts">
import { tv, type VariantProps } from 'tailwind-variants'
</script>
Important
Ne pas ajouter l'attribut setup
. Cette omission est volontaire.
La fonction tv
est un helper pour organiser les classes. Elle accepte un objet avec des clés base
et variants
. Le base
est une chaîne de classes partagées, tandis que variants
est un enregistrement avec des clés de variante et des valeurs de classe.
<script lang="ts">
// ...
const button = tv({
base: 'border-2 px-2.5 py-1.5 text-sm font-semibold focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
variants: {
variant: {
solid: 'border-transparent bg-blue-500 text-white hover:bg-blue-600 active:bg-blue-700',
outline: 'border-blue-500 text-blue-500 hover:bg-blue-50 active:bg-blue-100',
}
}
})
</script>
Dans la clé variants
, vous pourriez également inclure d'autres clés comme size
avec des variantes sm
et md
.
Ensuite, extrayez les variantes pour les utiliser dans les props de notre composant :
<script lang="ts">
// ...
type ButtonVariantProps = VariantProps<typeof button>
export interface ButtonProps {
label?: string
variant?: ButtonVariantProps['variant']
}
</script>
Ouvrez ensuite une nouvelle balise script
avec l'attribut setup
:
<script setup lang="ts">
withDefaults(defineProps<ButtonProps>(), {
variant: 'solid',
})
</script>
Nous avons divisé la balise script
en deux parties pour permettre des exports nommés. L'interface ButtonProps
est utilisée dans la fonction defineProps
et est exportée afin que les développeurs puissent l'étendre pour créer un nouveau composant. Ce détail est crucial pour construire une bibliothèque de composants robuste.
Ajoutez le template du composant :
<template>
<button :class="button({ variant })">
<slot>
{{ label }}
</slot>
</button>
</template>
Notre composant est prêt à être utilisé :
<Button variant="outline" label="Cliquez moi" />
Cependant, il n'est pas utilisable tant que nous n'avons pas construit notre bibliothèque de composants.
Construction de la Bibliothèque de Composants
Ensuite, nous allons construire la bibliothèque de composants. Commencez par créer un fichier vite.config.ts
à la racine du package huchet-vue
:
touch vite.config.ts
Cette configuration indique à Vite comment transpiler nos composants Vue.js en JavaScript pour être utilisé dans d'autres projets. Par défaut, Vite est conçu pour les applications web, pas pour les bibliothèques, donc nous allons l'ajuster à nos besoins.
Installez le plugin :
pnpm add -D @vitejs/plugin-vue
Modifiez le fichier vite.config.ts
avec la configuration suivante :
import Vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [Vue()],
build: {
lib: {
formats: ['es'],
name: 'huchet-vue',
fileName: (_, name) => `${name}.mjs`,
entry: {
index: resolve(__dirname, 'src/index.ts'),
},
},
rollupOptions: {
external: ['vue', 'tailwind-variants'],
},
},
})
Nous choisissons de ne pas inclure Vue.js et Tailwind Variants dans le bundle final pour plus de flexibilité, permettant aux utilisateurs d'utiliser leurs propres versions. Cela évite également la duplication dans le bundle final si l'utilisateur a déjà ces dépendances.
Ajoutez-les comme dépendances peer :
pnpm add --save-peer vue tailwind-variants
Note
Une dépendance peer n'est pas installée par le package mais doit être présente dans le projet de l'utilisateur. Cette flexibilité est particulièrement utile lorsqu'un package peut fonctionner dans des contextes variés.
Créez le fichier src/index.ts
, comme mentionné dans vite.config.ts
:
touch src/index.ts
Ce fichier exporte tous les composants de la bibliothèque :
export * from './Button'
Créez un nouveau index.ts
au niveau du composant :
touch src/Button/index.ts
Ce fichier sert d'API publique, exposant des éléments sélectionnés à l'utilisateur tout en gardant la structure interne cachée :
export {
type ButtonProps,
default as Button
} from './Button.vue'
Enfin, construisez la bibliothèque :
pnpm run build
La bibliothèque se compile sous le répertoire dist
sous le nom index.mjs
.
Exposition des Composants
Notre bibliothèque de composants est construite, mais pour être utilisable comme un package npm, nous devons définir certains champs dans le fichier package.json
. Ces champs fournissent des informations cruciales à Node sur la façon dont notre package doit être chargé :
{
"exports": {
".": {
"import": "./dist/index.mjs"
}
},
"main": "./dist/index.mjs"
}
Simple, non ? Le champ exports
informe Node des imports disponibles pour notre package. Dans notre cas, nous n'avons qu'un seul import—la racine de notre package—et c'est le fichier index.mjs
dans le dossier dist
. Le champ main
spécifie le point d'entrée de notre package. Ici, le point d'entrée est le fichier index.mjs
situé dans le dossier dist
.
Note
Puisque nous avons seulement un point d'entrée, nous pourrions nous fier uniquement au champ main
. Cependant, il est bon de définir le champ exports
pour garantir la compatibilité avec les versions passées de Node et préparer le support de points d'entrée multiples. Le champ exports
est plus moderne et puissant que main
.
Dans le prochain article, nous ajouterons des définitions de type à notre bibliothèque de composants pour offrir une meilleure expérience aux développeurs.
Merci de me lire ! Je m'appelle Estéban, et j'adore écrire sur le développement web.
Je code depuis plusieurs années maintenant, et j'apprends encore de nouvelles choses chaque jour. J'aime partager mes connaissances avec les autres, car j'aurais aimé avoir accès à des ressources aussi claires et complètes lorsque j'ai commencé à apprendre la programmation.
Si vous avez des questions ou souhaitez discuter, n'hésitez pas à commenter ci-dessous ou à me contacter sur Bluesky, X, et LinkedIn.
J'espère que vous avez apprécié cet article et appris quelque chose de nouveau. N'hésitez pas à le partager avec vos amis ou sur les réseaux sociaux, et laissez un commentaire ou une réaction ci-dessous—cela me ferait très plaisir ! Si vous souhaitez soutenir mon travail, vous pouvez me sponsoriser sur GitHub !