Odkryj architekturę wtyczek Vite i naucz się tworzyć własne wtyczki, aby usprawnić swój proces deweloperski. Opanuj kluczowe koncepcje na praktycznych przykładach.
Demistyfikacja Architektury Wtyczek Vite: Globalny Przewodnik po Tworzeniu Własnych Wtyczek
Vite, błyskawiczne narzędzie do budowania, zrewolucjonizowało rozwój frontendu. Jego szybkość i prostota wynikają w dużej mierze z potężnej architektury wtyczek. Architektura ta pozwala deweloperom rozszerzać funkcjonalność Vite i dostosowywać ją do specyficznych potrzeb projektu. Ten przewodnik oferuje kompleksowe omówienie systemu wtyczek Vite, umożliwiając Ci tworzenie własnych, niestandardowych wtyczek i optymalizację procesu deweloperskiego.
Zrozumienie Podstawowych Zasad Vite
Zanim zagłębimy się w tworzenie wtyczek, kluczowe jest zrozumienie fundamentalnych zasad Vite:
- Kompilacja na żądanie: Vite kompiluje kod tylko wtedy, gdy jest on żądany przez przeglądarkę, co znacznie skraca czas uruchamiania.
- Natywne ESM: Vite wykorzystuje natywne moduły ECMAScript (ESM) w trybie deweloperskim, eliminując potrzebę paczkowania (bundlingu) podczas developmentu.
- Build produkcyjny oparty na Rollup: Do buildów produkcyjnych Vite wykorzystuje Rollup, wysoce zoptymalizowany bundler, do generowania wydajnego i gotowego do produkcji kodu.
Rola Wtyczek w Ekosystemie Vite
Architektura wtyczek Vite została zaprojektowana z myślą o dużej rozszerzalności. Wtyczki mogą:
- Transformować kod (np. transpilacja TypeScript, dodawanie preprocesorów).
- Serwować niestandardowe pliki (np. obsługa zasobów statycznych, tworzenie modułów wirtualnych).
- Modyfikować proces budowania (np. optymalizacja obrazów, generowanie service workerów).
- Rozszerzać CLI Vite (np. dodawanie własnych komend).
Wtyczki są kluczem do dostosowania Vite do różnorodnych wymagań projektowych, od prostych modyfikacji po złożone integracje.
Architektura Wtyczek Vite: Dogłębna Analiza
Wtyczka Vite to w istocie obiekt JavaScript z określonymi właściwościami, które definiują jej zachowanie. Przyjrzyjmy się kluczowym elementom:
Konfiguracja Wtyczki
Plik `vite.config.js` (lub `vite.config.ts`) to miejsce, w którym konfigurujesz swój projekt Vite, w tym określasz, które wtyczki mają być używane. Opcja `plugins` przyjmuje tablicę obiektów wtyczek lub funkcji, które zwracają obiekty wtyczek.
// vite.config.js
import myPlugin from './my-plugin';
export default {
plugins: [
myPlugin(), // Wywołaj funkcję wtyczki, aby utworzyć jej instancję
],
};
Właściwości Obiektu Wtyczki
Obiekt wtyczki Vite może mieć kilka właściwości, które definiują jego zachowanie na różnych etapach procesu budowania. Oto zestawienie najczęstszych właściwości:
- name: Unikalna nazwa wtyczki. Jest to wymagane i pomaga w debugowaniu oraz rozwiązywaniu konfliktów. Przykład: `'my-custom-plugin'`
- enforce: Określa kolejność wykonywania wtyczki. Możliwe wartości to `'pre'` (uruchamiana przed wtyczkami rdzenia), `'normal'` (domyślna) i `'post'` (uruchamiana po wtyczkach rdzenia). Przykład: `'pre'`
- config: Pozwala na modyfikację obiektu konfiguracyjnego Vite. Otrzymuje konfigurację użytkownika oraz środowisko (tryb i komendę). Przykład: `config: (config, { mode, command }) => { ... }`
- configResolved: Wywoływana po pełnym rozwiązaniu konfiguracji Vite. Przydatna do uzyskania dostępu do finalnego obiektu konfiguracyjnego. Przykład: `configResolved(config) { ... }`
- configureServer: Zapewnia dostęp do instancji serwera deweloperskiego (podobnego do Connect/Express). Przydatna do dodawania niestandardowego middleware lub modyfikowania zachowania serwera. Przykład: `configureServer(server) { ... }`
- transformIndexHtml: Pozwala na transformację pliku `index.html`. Przydatna do wstrzykiwania skryptów, stylów lub metatagów. Przykład: `transformIndexHtml(html) { ... }`
- resolveId: Pozwala na przechwytywanie i modyfikowanie rozwiązywania modułów. Przydatna do niestandardowej logiki rozwiązywania modułów. Przykład: `resolveId(source, importer) { ... }`
- load: Pozwala na ładowanie niestandardowych modułów lub modyfikowanie zawartości istniejących modułów. Przydatna dla modułów wirtualnych lub niestandardowych loaderów. Przykład: `load(id) { ... }`
- transform: Transformuje kod źródłowy modułów. Działa podobnie do wtyczki Babel lub PostCSS. Przykład: `transform(code, id) { ... }`
- buildStart: Wywoływana na początku procesu budowania. Przykład: `buildStart() { ... }`
- buildEnd: Wywoływana po zakończeniu procesu budowania. Przykład: `buildEnd() { ... }`
- closeBundle: Wywoływana po zapisaniu paczki (bundle) na dysku. Przykład: `closeBundle() { ... }`
- writeBundle: Wywoływana przed zapisaniem paczki na dysku, pozwalając na jej modyfikację. Przykład: `writeBundle(options, bundle) { ... }`
- renderError: Pozwala na renderowanie niestandardowych stron błędów podczas developmentu. Przykład: `renderError(error, req, res) { ... }`
- handleHotUpdate: Pozwala na szczegółową kontrolę nad HMR. Przykład: `handleHotUpdate({ file, server }) { ... }`
Hooki Wtyczek i Kolejność Wykonywania
Wtyczki Vite działają poprzez serię hooków, które są uruchamiane na różnych etapach procesu budowania. Zrozumienie kolejności, w jakiej te hooki są wykonywane, jest kluczowe do pisania skutecznych wtyczek.
- config: Modyfikuj konfigurację Vite.
- configResolved: Uzyskaj dostęp do rozwiązanej konfiguracji.
- configureServer: Modyfikuj serwer deweloperski (tylko w trybie deweloperskim).
- transformIndexHtml: Transformuj plik `index.html`.
- buildStart: Początek procesu budowania.
- resolveId: Rozwiązuj identyfikatory modułów.
- load: Ładuj zawartość modułów.
- transform: Transformuj kod modułów.
- handleHotUpdate: Obsługuj Hot Module Replacement (HMR).
- writeBundle: Modyfikuj paczkę wyjściową przed zapisem na dysk.
- closeBundle: Wywoływana po zapisaniu paczki wyjściowej na dysk.
- buildEnd: Koniec procesu budowania.
Tworzenie Twojej Pierwszej Własnej Wtyczki Vite
Stwórzmy prostą wtyczkę Vite, która dodaje baner na górze każdego pliku JavaScript w buildzie produkcyjnym. Baner ten będzie zawierał nazwę i wersję projektu.
Implementacja Wtyczki
// banner-plugin.js
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
export default function bannerPlugin() {
return {
name: 'banner-plugin',
apply: 'build',
transform(code, id) {
if (!id.endsWith('.js')) {
return code;
}
const packageJsonPath = resolve(process.cwd(), 'package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
const banner = `/**\n * Project: ${packageJson.name}\n * Version: ${packageJson.version}\n */\n`;
return banner + code;
},
};
}
Wyjaśnienie:
- name: Definiuje nazwę wtyczki, 'banner-plugin'.
- apply: Określa, że ta wtyczka powinna działać tylko podczas procesu budowania. Ustawienie tej wartości na 'build' sprawia, że jest ona używana tylko w produkcji, unikając niepotrzebnego obciążenia podczas developmentu.
- transform(code, id):
- To jest rdzeń wtyczki. Przechwytuje kod (`code`) i ID (`id`) każdego modułu.
- Sprawdzenie warunkowe: `if (!id.endsWith('.js'))` zapewnia, że transformacja dotyczy tylko plików JavaScript. Zapobiega to przetwarzaniu innych typów plików (takich jak CSS czy HTML), co mogłoby powodować błędy lub nieoczekiwane zachowanie.
- Dostęp do package.json:
- `resolve(process.cwd(), 'package.json')` tworzy bezwzględną ścieżkę do pliku `package.json`. `process.cwd()` zwraca bieżący katalog roboczy, zapewniając użycie poprawnej ścieżki niezależnie od miejsca wykonania polecenia.
- `JSON.parse(readFileSync(packageJsonPath, 'utf-8'))` odczytuje i parsuje plik `package.json`. `readFileSync` odczytuje plik synchronicznie, a `'utf-8'` określa kodowanie, aby poprawnie obsłużyć znaki Unicode. Odczyt synchroniczny jest tutaj akceptowalny, ponieważ odbywa się raz na początku transformacji.
- Generowanie banera:
- ``const banner = `/**\n * Project: ${packageJson.name}\n * Version: ${packageJson.version}\n */\n`;`` tworzy ciąg znaków banera. Używa literałów szablonowych (backticks), aby łatwo osadzić nazwę i wersję projektu z pliku `package.json`. Sekwencje `\n` wstawiają nowe linie, aby poprawnie sformatować baner. Gwiazdka `*` jest poprzedzona znakiem ucieczki `\*`.
- Transformacja kodu: `return banner + code;` dodaje baner na początku oryginalnego kodu JavaScript. To jest ostateczny wynik zwracany przez funkcję transform.
Integracja Wtyczki
Zaimportuj wtyczkę do swojego pliku `vite.config.js` i dodaj ją do tablicy `plugins`:
// vite.config.js
import bannerPlugin from './banner-plugin';
export default {
plugins: [
bannerPlugin(),
],
};
Uruchamianie Builda
Teraz uruchom `npm run build` (lub polecenie budowania twojego projektu). Po zakończeniu budowania, sprawdź wygenerowane pliki JavaScript w katalogu `dist`. Zobaczysz baner na górze każdego pliku.
Zaawansowane Techniki Wtyczek
Oprócz prostych transformacji kodu, wtyczki Vite mogą wykorzystywać bardziej zaawansowane techniki, aby zwiększyć swoje możliwości.
Moduły Wirtualne
Moduły wirtualne pozwalają wtyczkom tworzyć moduły, które nie istnieją jako rzeczywiste pliki na dysku. Jest to przydatne do generowania dynamicznej zawartości lub dostarczania danych konfiguracyjnych do aplikacji.
// virtual-module-plugin.js
export default function virtualModulePlugin(options) {
const virtualModuleId = 'virtual:my-module';
const resolvedVirtualModuleId = '\0' + virtualModuleId; // Prefiks \0 zapobiega przetwarzaniu przez Rollup
return {
name: 'virtual-module-plugin',
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId;
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `export default ${JSON.stringify(options)};`;
}
},
};
}
W tym przykładzie:
- `virtualModuleId` to ciąg znaków reprezentujący identyfikator modułu wirtualnego.
- `resolvedVirtualModuleId` jest poprzedzony prefiksem `\0`, aby zapobiec przetwarzaniu go przez Rollup jako prawdziwego pliku. Jest to konwencja używana we wtyczkach Rollup.
- `resolveId` przechwytuje rozwiązywanie modułów i zwraca rozwiązany identyfikator modułu wirtualnego, jeśli żądany ID pasuje do `virtualModuleId`.
- `load` przechwytuje ładowanie modułów i zwraca kod modułu, jeśli żądany ID pasuje do `resolvedVirtualModuleId`. W tym przypadku generuje moduł JavaScript, który eksportuje `options` jako domyślny eksport.
Używanie Modułu Wirtualnego
// vite.config.js
import virtualModulePlugin from './virtual-module-plugin';
export default {
plugins: [
virtualModulePlugin({ message: 'Hello from virtual module!' }),
],
};
// main.js
import message from 'virtual:my-module';
console.log(message.message); // Wynik: Hello from virtual module!
Transformacja Pliku Index HTML
Hook `transformIndexHtml` pozwala na modyfikację pliku `index.html`, na przykład wstrzykując skrypty, style lub metatagi. Jest to przydatne do dodawania śledzenia analitycznego, konfigurowania metadanych mediów społecznościowych lub dostosowywania struktury HTML.
// inject-script-plugin.js
export default function injectScriptPlugin() {
return {
name: 'inject-script-plugin',
transformIndexHtml(html) {
return html.replace(
'