API pour plugin
Les plugins Vite étendent la très bonne interface pour plugin de Rollup avec quelques options spécifiques à Vite. L’intérêt est que vous pouvez créer un seul plugin et qu’il fonctionnera à la fois en développement et pour la compilation.
Il est recommandé de lire la documentation des plugins Rollup (en anglais) avant ce qui suit.
Créer un plugin
Vite s’efforce de permettre l’utilisation des patterns les plus courants sans qu’il n’y ait besoin de plugin, alors avant d’en faire un nouveau assurez-vous d’avoir bien lu le guide des fonctionnalités pour savoir si votre besoin n’est pas déjà couvert. Vérifiez aussi parmis les plugins de la communauté, à la fois sous la forme de plugins Rollup compatibles ou de plugins spécifiques à Vite.
Lorsque vous créez un plugin, vous pouvez le mettre inline dans votre vite.config.js. Il n’y a pas spécialement besoin de créer un nouveau package. Une fois que vous constatez qu’un plugin a été utile pour l’un de vos projets, vous pouvez envisager de le partager pour aider d’autres personnes dans l’écosystème.
TIP
Si vous apprenez, débuggez, ou écrivez des plugins nous recommandons d’inclure vite-plugin-inspect à votre projet. Il vous permet d’inspecter les états intermédiaires des plugins Vite. Après l’avoir installé, vous pouvez aller voir localhost:3000/__inspect/ pour inspecter les modules et les transformations de votre projet. Les instructions d’installation se trouvent dans la documentation de vite-plugin-inspect. 
Conventions
Si le plugin n’utilise pas de hook spécifique à Vite et peut être implémenté en tant que plugin Rollup compatible, alors il est recommandé d’utiliser les conventions de nommage des plugins Rollup :
- Les plugins doivent avoir un nom clair avec un préfixe
rollup-plugin-. - Les
keywordsdu package.json doivent inclurerollup-pluginetvite-plugin.
Cela permet que le plugin soit aussi utilisable par des projets basés sur Rollup ou WMR.
Pour les plugins uniquement compatibles avec Vite :
- Les plugins doivent avoir un nom clair avec un préfixe
vite-plugin-. - Les
keywordsdu package.json doivent inclurevite-plugin. - La documentation du plugin doit inclure une section expliquant pourquoi le plugin est uniquement compatible avec Vite (par exemple, parce qu’il utilise un hook spécifique à Vite).
Si votre plugin ne fonctionne qu’avec un framework en particulier, son nom doit faire partie du préfixe :
vite-plugin-vue-pour les plugins Vuevite-plugin-react-pour les plugins Reactvite-plugin-svelte-pour les plugins Svelte
Voir aussi la convention pour les modules virtuels.
Configuration des plugins
Les utilisateurs ajouteront les plugins aux devDependencies du projet et les configureront en utilisant l’option plugins.
// vite.config.js
import vitePlugin from 'vite-plugin-feature'
import rollupPlugin from 'rollup-plugin-feature'
export default defineConfig({
plugins: [vitePlugin(), rollupPlugin()]
})
Les plugins falsy seront ignorés, ce qui peut être utilisé pour activer ou désactiver des plugins.
plugins accepte aussi des presets tels que plusieurs plugins dans un seul élément. C’est pratique lorsque des fonctionnalités complexes (comme l’intégration d’un framework) sont implémentées à l’aide de plusieurs plugins. La liste sera aplatie par Vite.
// framework-plugin
import frameworkRefresh from 'vite-plugin-framework-refresh'
import frameworkDevtools from 'vite-plugin-framework-devtools'
export default function framework(config) {
return [frameworkRefresh(config), frameworkDevTools(config)]
}
// vite.config.js
import { defineConfig } from 'vite'
import framework from 'vite-plugin-framework'
export default defineConfig({
plugins: [framework()]
})
Exemples simples
TIP
C’est une convention répandue de créer des plugins Vite/Rollup à l’aide d’une fonction fabrique (factory) qui retourne l’objet plugin. La fonction peut accepter des options qui permettent aux utilisateurs de configurer le comportement du plugin.
Transformer les types de fichiers custom
const fileRegex = /\.(my-file-ext)$/
export default function myPlugin() {
return {
name: 'transform-file',
transform(src, id) {
if (fileRegex.test(id)) {
return {
code: compileFileToJS(src),
map: null // fournissez une sourcemap si possible
}
}
}
}
}
Importer un fichier virtuel
Voir l’exemple de la section suivante.
Convention pour les modules virtuels
Les modules virtuels sont un procédé utile qui permet de passer des informations aux fichiers source au moment de la compilation avec une syntaxe d’import de module ES normale.
export default function myPlugin() {
const virtualModuleId = '@my-virtual-module'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return {
name: 'my-plugin', // requis, sera utilisé pour les avertissements et les
// erreurs
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `export const msg = "from virtual module"`
}
}
}
}
Ce qui permet d’importer le module en JavaScript :
import { msg } from '@my-virtual-module'
console.log(msg)
La convention de Vite pour les modules virtuels est de préfixer le chemin visible par l’utilisateur par virtual:. Si possible le nom du plugin devrait être utilisé comme un namespace pour éviter d’entrer en conflit avec les autres plugins de l’écosystème. Par exemple, vite-plugin-posts pourrait demander aux utilisateurs d’importer un module virtuel virtual:posts ou virtual:posts/helpers pour obtenir des informations au moment de la compilation. En interne, les plugins qui utilisent des modules virtuels doivent préfixer l’identifiant par \0 lorsqu’ils résolvent l’identifiant (c’est une convention de l’écosystème Rollup). Cela évite que d’autres plugins essayent de traiter le même identifiant (comme la résolution des nœuds), et les fonctionnalités intégrées à Vite comme les sourcemaps peuvent se servir de cette information pour différencier les modules virtuels des fichiers classiques. \0 n’est pas un caractère autorisé dans les URLs d’import alors nous devons le remplacer pendant l’analyse de l’import. Dans le navigateur, un identifiant virtuel \0{id} sera encodé sous la forme /@id/__x00__{id} pour le développement. Cet identifiant sera à nouveau décodé avant d’entrer dans la pipeline de plugins, alors cette mécanique n’est pas visible par les hooks de plugins.
Notez que les modules directement tirés d’un vrai fichier, comme c’est le cas d’un module de script dans un composant à fichier unique (SFC) (un composant à fichier unique .vue ou .svelte par exemple) n’ont pas besoin de suivre cette convention. Les composants à fichier unique génèrent en général une série de sous-modules lorsqu’ils sont traités mais le code de ceux-ci peut-être relié au système de fichiers. Utiliser \0 pour ces sous-modules empêcherait les sourcemaps de fonctionner correctement.
Hooks universels
Pendant le développement, le serveur de Vite crée un conteneur de plugin qui invoque les hooks de compilation de Rollup de la même façon que le fait Rollup.
Les hooks suivants sont appelés une fois lors du démarrage du serveur :
Les hooks suivants sont appelés à chaque requête de module entrante :
Les hooks suivants sont appelés quand le serveur se ferme :
Notez que le hook moduleParsed n’est pas appelé pendant le développement, parce que Vite évite de parser lorsque cela requiert tout un arbre de la syntaxe abstraite (AST) pour des raisons de performance.
Les hooks de génération de l’output (sauf closeBundle) ne sont pas appelés durant le développement. Vous pouvez partir du principe que le serveur de développement de Vite appelle seulement rollup.rollup() et pas bundle.generate().
Hooks spécifiques à Vite
Les plugins Vite peuvent aussi fournir des hooks qui servent uniquement pour Vite. Ces hooks sont ignorés par Rollup.
config
Type :
(config: UserConfig, env: { mode: string, command: string }) => UserConfig | null | voidGenre :
asynchrone,séquentielModifie la configuration de Vite avant qu’elle ne soit résolue. Le hook reçoit la configuration brute de l’utilisateur (les options de l’interface en ligne de commande fusionnées avec le fichier de configuration) et l’environnement de configuration courant qui expose le
modeet lacommand. Il peut retourner un objet de configuration incomplet qui sera fusionné avec la configuration existante, ou directement muter la configuration (si la fusion par défaut ne permet pas d’atteindre le résultat souhaité).Exemple :
// retourne une configuration incomplète (recommandé) const partialConfigPlugin = () => ({ name: 'return-partial', config: () => ({ alias: { foo: 'bar' } }) }) // mute directement la configuration (à utiliser seulement si la fusion ne // convient pas) const mutateConfigPlugin = () => ({ name: 'mutate-config', config(config, { command }) { if (command === 'build') { config.root = __dirname } } })Note
Les plugins utilisateur sont résolus avant que ce hook ne soit exécuté alors injecter d’autres plugins dans le hook
confign’aura aucun effet.
configResolved
Type :
(config: ResolvedConfig) => void | Promise<void>Genre :
asynchrone,parallèleAppelé après que la configuration de Vite ne soit résolue. Utilisez ce hook pour lire et stocker la configuration résolue. Il est aussi utile quand le plugin doit faire quelque chose de différent selon la commande utilisée.
Exemple :
const examplePlugin = () => { let config return { name: 'read-config', configResolved(resolvedConfig) { // stocker la configuration résolue config = resolvedConfig }, // utiliser la configuration stockée dans un autre hook transform(code, id) { if (config.command === 'serve') { // développement : plugin invoqué par le serveur de développement } else { // build : plugin invoqué par Rollup } } } }Notez que la valeur de
commandestserveen développement (sur l’interface en ligne de commandevite,vite devetvite servesont des alias).
configureServer
Type :
(server: ViteDevServer) => (() => void) | void | Promise<(() => void) | void>Genre :
asynchrone,séquentielVoir aussi : ViteDevServer
Utilisez ce hook pour configurer le serveur de développement. Le cas d’usage le plus courant est d’ajouter des middlewares spécifiques à l’application connect interne :
const myPlugin = () => ({ name: 'configure-server', configureServer(server) { server.middlewares.use((req, res, next) => { // manipuler les requêtes… }) } })Injecter des middlewares après-coup
Le hook
configureServerest appelé avant que les middlewares internes ne soient installés, alors les middlewares spécifiques s’exécuteront avant les middlewares internes par défaut. Si vous voulez injecter un middleware après les middlewares internes, vous pouvez retourner une fonction dansconfigureServer, qui sera appelée après que les middlewares internes soient installés :const myPlugin = () => ({ name: 'configure-server', configureServer(server) { // retourne un hook qui est appelé après que les middlewares internes // soient installés return () => { server.middlewares.use((req, res, next) => { // manipuler les requêtes… }) } } })Stocker l’accès au serveur
Dans certaines cas, d’autres hooks de plugins peuvent avoir besoin d’accéder à l’instance du serveur de développement (par exemple pour accéder au serveur web socket, au watcher du système de fichiers, ou au graphe des modules). Ce hook peut aussi être utilisé pour stocker l’instance du serveur et s’en servir dans d’autres hooks :
const myPlugin = () => { let server return { name: 'configure-server', configureServer(_server) { server = _server }, transform(code, id) { if (server) { // utiliser le serveur… } } } }Notez que
configureServern’est pas appelé au moment de la compilation de production alors vos autres hooks doivent gérer le cas où l’instance de serveur est absente.
transformIndexHtml
Type :
IndexHtmlTransformHook | { enforce?: 'pre' | 'post', transform: IndexHtmlTransformHook }Genre :
asynchrone,séquentielHook dédié au fait de transformer
index.html. Le hook reçoit le HTML actuel sous la forme d’une chaîne de caractères et un contexte de transformation. Le contexte expose l’instance duViteDevServerpendant le développement, et expose le bundle de sortie de Rollup pendant la compilation.Le hook peut être asynchrone et retourne un des formats suivants :
- Chaîne de caractères HTML transformée.
- Une liste d’objets décrivant des éléments (
{ balise, attributs, enfants }) à injecter dans le HTML existant. Chaque élément peut aussi spécifier où il doit être injecté (par défaut il est ajouté à la fin de<head>). - Un objet contenant les deux (
{ html, éléments }).
Exemple basique :
const htmlPlugin = () => { return { name: 'html-transform', transformIndexHtml(html) { return html.replace( /<title>(.*?)<\/title>/, `<title>Title replaced!</title>` ) } } }Signature complète du hook :
type IndexHtmlTransformHook = ( html: string, ctx: { path: string filename: string server?: ViteDevServer bundle?: import('rollup').OutputBundle chunk?: import('rollup').OutputChunk } ) => | IndexHtmlTransformResult | void | Promise<IndexHtmlTransformResult | void> type IndexHtmlTransformResult = | string | HtmlTagDescriptor[] | { html: string tags: HtmlTagDescriptor[] } interface HtmlTagDescriptor { tag: string attrs?: Record<string, string | boolean> children?: string | HtmlTagDescriptor[] /** * par défaut : 'head-prepend' */ injectTo?: 'head' | 'body' | 'head-prepend' | 'body-prepend' }
handleHotUpdate
Type :
(ctx: HmrContext) => Array<ModuleNode> | void | Promise<Array<ModuleNode> | void>Gère le remplacement des modules à la volée (HMR). Le hook reçoit un objet de contexte avec la signature suivante :
interface HmrContext { file: string timestamp: number modules: Array<ModuleNode> read: () => string | Promise<string> server: ViteDevServer }modulesest une liste des modules affectés par le fichier modifié. C’est une liste car un fichier unique peut correspondre à plusieurs modules (comme les composants à fichier unique par exemple).readest une fonction de lecture asynchrone qui retourne le contenu du fichier. Elle est fournie car sur certains systèmes, le callback lorsqu’un fichier est modifié peut être déclenché trop rapidement (avant que l’éditeur ne finisse de se mettre à jour) et utiliserfs.readFiledirectement retournera un contenu vide. La fonction read permet de normaliser ce comportement.
Le hook peut choisir de :
Filtrer et réduire la liste des modules affectés pour que le remplacement des modules à la volée soit plus précis.
Retourner une liste vide et faire le remplacement de façon complétement custom en envoyant des évènements au client :
handleHotUpdate({ server }) { server.ws.send({ type: 'custom', event: 'special-update', data: {} }) return [] }Le code client doit utiliser le handler correspondant à l’aide de l’API du rafraîchissement des modules (qui peut être injecté par le hook
transformdu même plugin) :if (import.meta.hot) { import.meta.hot.on('special-update', (data) => { // faire le remplacement custom }) }
Ordre du plugin
Un plugin Vite peut également spécifier une propriété enforce (à la manière des loaders webpack) pour ajuster son ordre dans la liste. La valeur d’enforce peut être soit "pre" soit "post". Les plugins résolus seront traités dans l’ordre suivant :
- Alias.
- Plugins utilisateur avec
enforce: 'pre'. - Plugins internes à Vite.
- Plugins utilisateur sans valeur pour enforce.
- Plugins de compilation de Vite.
- Plugins utilisateur avec
enforce: 'post'. - Plugins de compilation finaux de Vite (minification, manifeste, reporting).
Application conditionnelle
Par défaut les plugins sont invoqués à la fois pour serve et build. Dans les cas où un plugin ne doit être appliqué conditionnellement que pour serve ou build, utilisez la propriété apply pour seulement l’invoquer durant 'build' ou 'serve' :
function myPlugin() {
return {
name: 'build-only',
apply: 'build' // ou 'serve'
}
}
Un fonction peut aussi être utilisée, pour plus de contrôle :
apply(config, { command }) {
// appliquer seulement pour la compilation et pas pour le rendu côté serveur
return command === 'build' && !config.build.ssr
}
Compatibilité des plugins Rollup
Une bonne quantité de plugins Rollup fonctionnera directement comme plugin Vite (par exemple @rollup/plugin-alias ou @rollup/plugin-json), mais pas tous, puisque certains hooks de plugin n’ont pas de sens dans le contexte d’un serveur de développement sans bundling.
En général, tant qu’un plugin Rollup respecte les critères suivants il devrait fonctionner comme plugin Vite :
- Il n’utilise pas le hook
moduleParsed. - Sa phase de bundling et sa phase d’output sont découplées.
Si un plugin Rollup n’a de sens que pour la phase de compilation, alors il peut être spécifié sous build.rollupOptions.plugins.
Vous pouvez aussi agrémenter un plugin Rollup existant de propriétés propres à Vite :
// vite.config.js
import example from 'rollup-plugin-example'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
{
...example(),
enforce: 'post',
apply: 'build'
}
]
})
Lisez la liste des plugins Rollup compatibles avec Vite pour plus d’informations sur les plugins officiels Rollup et pour des instructions d’utilisation (en anglais).
Normalisation des chemins
Vite normalise les chemins lorsqu’il résout les identifiants de manière à utiliser les séparateurs POSIX ( / ) tout en préservant le volume sur Windows. D’un autre côté, Rollup garde les chemins résolus intacts par défaut, alors les identifiants résolus ont des séparateurs win32 ( \ ) sur Windows. Cependant, les plugins Rollup utilisent la fonction utilitaire normalizePath de @rollup/pluginutils, qui convertit les séparateurs au format POSIX avant de faire des comparaisons. Cela signifie que lorsque ces plugins sont utilisés dans Vite, les patterns de configuration include, exclude et similaires peuvent faire de la comparaison avec les identifiants résolus.
Donc les plugins Vite, lorsqu’ils comparent des chemins avec des identifiants résolus, doivent impérativement normaliser les chemins de manière à ce qu’ils utilisent des séparateurs POSIX. Une fonction utilitaire normalizePath équivalente est exportée par le module vite.
import { normalizePath } from 'vite'
normalizePath('foo\\bar') // 'foo/bar'
normalizePath('foo/bar') // 'foo/bar'