Vite zu Ihrer bestehenden Web App hinzufügen

Avatar of Adam Rackis
Adam Rackis am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Vite (ausgesprochen „Vit“) ist ein relativ neuer JavaScript-Bundler. Er ist "batteries included", erfordert fast keine Konfiguration, um nützlich zu sein, und bietet viele Konfigurationsmöglichkeiten. Oh – und er ist schnell. Unglaublich schnell.

Dieser Beitrag führt Sie durch den Prozess der Konvertierung eines bestehenden Projekts in Vite. Wir behandeln Themen wie Aliase, das Shimmen der Webpack-dotenv-Verarbeitung und Server-Proxying. Mit anderen Worten, wir sehen uns an, wie man ein Projekt von seinem bestehenden Bundler zu Vite migriert. Wenn Sie stattdessen ein neues Projekt starten möchten, sollten Sie zu deren Dokumentation springen.

Lang, kurz gesagt: Die CLI fragt nach Ihrem bevorzugten Framework – React, Preact, Svelte, Vue, Vanilla oder sogar lit-html – und ob Sie TypeScript wünschen, und liefert Ihnen dann ein voll funktionsfähiges Projekt.

Zuerst gerüstet! Wenn Sie daran interessiert sind, Vite in ein Legacy-Projekt zu integrieren, würde ich Ihnen trotzdem empfehlen, ein leeres Projekt zu erstellen und es ein wenig zu untersuchen. Manchmal werde ich einige Codeblöcke einfügen, aber die meisten davon stammen direkt aus der Standard-Vite-Vorlage.

Unser Anwendungsfall

Was wir betrachten, basiert auf meiner eigenen Erfahrung bei der Migration des Webpack-Builds meines Buchlistenprojekts (Repo). An diesem Projekt ist nichts Besonderes, aber es ist ziemlich groß und alt und stützte sich stark auf Webpack. In diesem Sinne ist es eine gute Gelegenheit, einige der nützlicheren Konfigurationsoptionen von Vite in Aktion zu sehen, während wir darauf migrieren.

Was wir nicht brauchen werden

Einer der überzeugendsten Gründe, sich für Vite zu entscheiden, ist, dass es bereits viel „out of the box“ erledigt und viele der Verantwortlichkeiten anderer Frameworks integriert, sodass es weniger Abhängigkeiten und eine etabliertere Basis für Konfigurationen und Konventionen gibt.

Anstatt also damit zu beginnen, aufzuzählen, was wir zum Starten benötigen, gehen wir all die gängigen Webpack-Dinge durch, die wir nicht benötigen, da Vite sie uns kostenlos zur Verfügung stellt.

Laden von statischen Assets

Normalerweise müssen wir in Webpack etwas wie das hier hinzufügen

{
  test: /\.(png|jpg|gif|svg|eot|woff|woff2|ttf)$/,
  use: [
    {
      loader: "file-loader"
    }
  ]
}

Dies nimmt alle Verweise auf Schriftdateien, Bilder, SVG-Dateien usw. auf und kopiert sie in Ihren Dist-Ordner, sodass sie von Ihren neuen Bundles referenziert werden können. Dies ist bei Vite Standard.

Stile

Ich sage „Styles“ im Gegensatz zu „CSS“ absichtlich hier, denn mit Webpack haben Sie vielleicht so etwas

{
  test: /\.s?css$/,
  use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
},

// later

new MiniCssExtractPlugin({ filename: "[name]-[contenthash].css" }),

…was es der Anwendung ermöglicht, CSS- oder SCSS-Dateien zu importieren. Sie werden es leid sein, mich das sagen zu hören, aber Vite unterstützt dies von Haus aus. Stellen Sie einfach sicher, dass Sie Sass selbst in Ihrem Projekt installieren, und Vite kümmert sich um den Rest.

Transpilierung / TypeScript

Es ist wahrscheinlich, dass Ihr Code TypeScript und/oder nicht standardmäßige JavaScript-Funktionen wie JSX verwendet. Wenn dies der Fall ist, müssen Sie Ihren Code transpilieren, um diese Dinge zu entfernen und einfachen alten JavaScript zu erzeugen, den ein Browser (oder ein JavaScript-Parser) verstehen kann. In Webpack würde das etwa so aussehen

{
  test: /\.(t|j)sx?$/,
  exclude: /node_modules/,
  loader: "babel-loader"
},

…mit einer entsprechenden Babel-Konfiguration zur Angabe der richtigen Plugins, die für mich so aussah

{
  "presets": ["@babel/preset-typescript"],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-syntax-dynamic-import",
    "@babel/plugin-proposal-optional-chaining",
    "@babel/plugin-proposal-nullish-coalescing-operator"
  ]
}

Obwohl ich wahrscheinlich schon vor Jahren mit der Verwendung dieser ersten beiden Plugins hätte aufhören können, spielt es keine Rolle, denn wie Sie sich sicher schon gedacht haben, macht Vite all das für uns. Es nimmt Ihren Code, entfernt TypeScript und JSX und erzeugt Code, der von modernen Browsern unterstützt wird.

Wenn Sie ältere Browser unterstützen möchten (und ich sage nicht, dass Sie das tun sollten), dann gibt es ein Plugin dafür.

node_modules

Überraschenderweise verlangt Webpack von Ihnen, dass Sie ihm mitteilen, Importe aus node_modules aufzulösen, was wir mit diesem tun

resolve: {
  modules: [path.resolve("./node_modules")]
}

Wie erwartet, macht Vite dies bereits.

Produktionsmodus

Eines der häufigsten Dinge, die wir in Webpack tun, ist die Unterscheidung zwischen Produktions- und Entwicklungsumgebungen, indem wir manuell eine mode-Eigenschaft übergeben, wie hier

mode: isProd ? "production" : "development",

…was wir normalerweise mit etwas wie diesem ermitteln

const isProd = process.env.NODE_ENV == "production";

Und natürlich setzen wir diese Umgebungsvariable über unseren Build-Prozess.

Vite behandelt dies etwas anders und stellt uns verschiedene Befehle zur Verfügung, die wir für Entwicklungs-Builds versus Produktions-Builds ausführen können, was wir gleich besprechen werden.

Dateierweiterungen

Auf die Gefahr hin, den Punkt zu überstrapazieren, weise ich kurz darauf hin, dass Vite auch nicht von Ihnen verlangt, jede von Ihnen verwendete Dateierweiterung anzugeben.

resolve: {
  extensions: [".ts", ".tsx", ".js"],
}

Richten Sie einfach den richtigen Vite-Projekttyp ein, und Sie sind bereit.

Rollup-Plugins sind kompatibel!

Dies ist ein so wichtiger Punkt, dass ich ihn in einem eigenen Abschnitt hervorheben wollte. Wenn Sie am Ende dieses Blogbeitrags noch Webpack-Plugins haben, die Sie in Ihrer Vite-App ersetzen müssen, versuchen Sie, ein äquivalentes Rollup-Plugin zu finden und dieses zu verwenden. Sie haben richtig gelesen: Rollup-Plugins sind bereits (oder normalerweise zumindest) mit Vite kompatibel. Einige Rollup-Plugins tun natürlich Dinge, die nicht mit der Funktionsweise von Vite vereinbar sind – aber im Allgemeinen sollten sie einfach funktionieren.

Weitere Informationen finden Sie in der Dokumentation.

Ihr erstes Vite-Projekt

Denken Sie daran, wir migrieren ein bestehendes Legacy-Webpack-Projekt zu Vite. Wenn Sie etwas Neues bauen, ist es besser, ein neues Vite-Projekt zu starten und von dort aus weiterzumachen. Das gesagt, der anfängliche Code, den ich Ihnen zeige, ist im Grunde direkt von dem kopiert, was Vite aus einem neuen Projekt generiert, daher könnte es auch für Sie eine gute Idee sein, einen Moment für die Erstellung eines neuen Projekts einzuplanen, um die Prozesse zu vergleichen.

Der HTML-Einstiegspunkt

Ja, Sie haben richtig gelesen. Anstatt die HTML-Integration wie bei Webpack hinter einem Plugin zu verstecken, ist Vite HTML-zentriert. Es erwartet eine HTML-Datei mit einem Skript-Tag für Ihren JavaScript-Einstiegspunkt und generiert von dort alles.

Hier ist die HTML-Datei (von Vite erwartet, dass sie index.html heißt), mit der wir beginnen

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>The GOAT of web apps</title>
  </head>
  <body>
    <div id="home"></div>
    <script type="module" src="/reactStartup.tsx"></script>
  </body>
</html>

Beachten Sie, dass das <script>-Tag auf /reactStartup.tsx verweist. Passen Sie dies nach Bedarf an Ihren eigenen Einstiegspunkt an.

Installieren wir ein paar Dinge, wie ein React-Plugin

npm i vite @vitejs/plugin-react @types/node

Wir erstellen auch die folgende vite.config.ts direkt neben der index.html-Datei im Projektverzeichnis.

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()]
});

Zuletzt fügen wir ein paar neue npm-Skripte hinzu

"dev": "vite",
"build": "vite build",
"preview": "vite preview",

Starten wir nun den Vite-Entwicklungsserver mit npm run dev. Er ist unglaublich schnell und erstellt inkrementell alles, was er benötigt, basierend auf dem, was angefordert wird.

Aber leider schlägt es fehl. Zumindest für den Moment.

Screenshot of a terminal screen with a dark background and light text. There is an error in read that says there was an error when starting the development server.

Wir werden uns gleich damit befassen, wie man Aliase einrichtet, aber lassen Sie uns stattdessen zunächst unsere reactStartup-Datei (oder wie auch immer Ihre Einstiegsdatei heißt) wie folgt ändern

import React from "react";
import { render } from "react-dom";

render(
  <div>
    <h1>Hi there</h1>
  </div>,
  document.getElementById("home")
);

Jetzt können wir unseren npm run dev-Befehl ausführen und zu localhost:3000 navigieren.

Screenshot of a terminal window with a black background and light text. Green text says the development server is running at localhost.
Screenshot of a blank white page that says hi there in black in a default serif font.

Hot Module Reloading (HMR)

Jetzt, da der Entwicklungsserver läuft, versuchen Sie, Ihren Quellcode zu ändern. Die Ausgabe sollte fast sofort über das HMR von Vite aktualisiert werden. Dies ist eine der schönsten Funktionen von Vite. Es macht die Entwicklungserfahrung so viel angenehmer, wenn Änderungen sofort reflektiert zu werden scheinen, anstatt warten oder sie sogar selbst auslösen zu müssen.

Der Rest dieses Beitrags wird all die Dinge behandeln, die ich tun musste, um meine eigene App mit Vite zu erstellen und auszuführen. Ich hoffe, einige davon sind für Sie relevant!

Aliase

Es ist nicht ungewöhnlich, dass Webpack-basierte Projekte eine Konfiguration wie diese haben

resolve: {
  alias: {
    jscolor: "util/jscolor.js"
  },
  modules: [path.resolve("./"), path.resolve("./node_modules")]
}

Dies richtet einen Alias für jscolor unter dem angegebenen Pfad ein und weist Webpack an, sowohl im Stammordner (./) als auch in node_modules nachzulösen, wenn Importe aufgelöst werden. Dies ermöglicht uns Importe wie diese

import { thing } from "util/helpers/foo"

…irgendwo in unserem Komponentenbaum, vorausgesetzt, es gibt einen util-Ordner ganz oben.

Vite erlaubt Ihnen nicht, einen ganzen Ordner für die Auflösung wie diese anzugeben, aber es erlaubt Ihnen, Aliase anzugeben, die denselben Regeln folgen wie das @rollup/plugin-alias

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

import path from "path";

export default defineConfig({
  resolve: {
    alias: {
      jscolor: path.resolve("./util/jscolor.js"),
      app: path.resolve("./app"),
      css: path.resolve("./css"),
      util: path.resolve("./util")
    }
  },
  plugins: [react()]
});

Wir haben einen Abschnitt resolve.alias hinzugefügt, einschließlich Einträgen für alles, was wir aliasen müssen. Unser jscolor-Util ist auf das relevante Modul gesetzt, und wir haben Aliase für unsere Top-Level-Verzeichnisse. Jetzt können wir von app/, css*/* und util/ aus jeder Komponente, überall importieren.

Beachten Sie, dass diese Aliase nur für die Wurzel des Imports gelten, z. B. util/foo. Wenn Sie einen anderen util-Ordner tiefer in Ihrem Baum haben und ihn mit diesem referenzieren

import { thing } from "./helpers/util";

…dann wird der obige Alias diesen nicht durcheinanderbringen. Diese Unterscheidung ist nicht gut dokumentiert, aber Sie können sie im Rollup-Alias-Plugin sehen. Der Alias von Vite entspricht demselben Verhalten.

Umgebungsvariablen

Vite unterstützt natürlich Umgebungsvariablen. Es liest Konfigurationswerte aus Ihren .env-Dateien während der Entwicklung oder aus process.env und injiziert sie in Ihren Code. Leider funktionieren die Dinge etwas anders, als Sie es vielleicht gewohnt sind. Erstens ersetzt es nicht process.env.FOO, sondern import.meta.env.FOO. Nicht nur das, sondern es ersetzt standardmäßig nur Variablen, die mit VITE_ beginnen. Also würde import.meta.env.VITE_FOO tatsächlich ersetzt werden, aber nicht mein ursprüngliches FOO. Dieses Präfix kann konfiguriert werden, aber nicht auf einen leeren String gesetzt werden.

Für ein Legacy-Projekt könnten Sie alle Ihre Umgebungsvariablen per grep und replace ändern, um import.meta.env zu verwenden, dann ein VITE_-Präfix hinzufügen, Ihre .env-Dateien aktualisieren und die Umgebungsvariablen in Ihrem CI/CD-System aktualisieren. Oder Sie können das klassischere Verhalten konfigurieren, process.env.ANYTHING mit Werten aus einer .env-Datei in der Entwicklung oder dem tatsächlichen process.env-Wert in der Produktion zu ersetzen.

So geht's. Die define-Funktion von Vite ist im Grunde das, was wir brauchen. Dies registriert globale Variablen während der Entwicklung und führt eine reine Textsubstitution für die Produktion durch. Wir müssen die Dinge so einrichten, dass wir unsere .env-Datei im Entwicklungsmodus manuell lesen und das process.env-Objekt im Produktionsmodus, und dann die entsprechenden define-Einträge hinzufügen.

Bauen wir das alles in ein Vite-Plugin ein. Installieren Sie zuerst npm i dotenv.

Schauen wir uns nun den Code für das Plugin an

import dotenv from "dotenv";

const isProd = process.env.NODE_ENV === "production";
const envVarSource = isProd ? process.env : dotenv.config().parsed;

export const dotEnvReplacement = () => {
  const replacements = Object.entries(envVarSource).reduce((obj, [key, val]) => {
    obj[`process.env.${key}`] = `"${val}"`;
    return obj;
  }, {});

  return {
    name: "dotenv-replacement",
    config(obj) {
      obj.define = obj.define || {};
      Object.assign(obj.define, replacements);
    }
  };
};

Vite setzt process.env.NODE_ENV für uns, also müssen wir nur das überprüfen, um zu sehen, in welchem Modus wir uns befinden.

Jetzt erhalten wir die tatsächlichen Umgebungsvariablen. Wenn wir in der Produktion sind, greifen wir auf process.env selbst zu. Wenn wir in der Entwicklung sind, bitten wir dotenv, unsere .env-Datei zu greifen, zu parsen und ein Objekt mit allen Werten zurückzubekommen.

Unser Plugin ist eine Funktion, die ein Vite-Plugin-Objekt zurückgibt. Wir injizieren unsere Umgebungswerte in ein neues Objekt, das process.env. vor dem Wert hat, und geben dann unser tatsächliches Plugin-Objekt zurück. Es gibt eine Reihe von Hooks, die wir verwenden können. Hier benötigen wir jedoch nur den config-Hook, der es uns ermöglicht, das aktuelle Konfigurationsobjekt zu ändern. Wir fügen einen define-Eintrag hinzu, wenn keiner vorhanden ist, und fügen dann all unsere Werte hinzu.

Bevor wir fortfahren, möchte ich jedoch darauf hinweisen, dass die Einschränkungen der Vite-Umgebungsvariablen, die wir umgehen, einen Grund haben. Der obige Code ist, wie Bundler häufig konfiguriert werden, aber das bedeutet immer noch, dass jeder zufällige Wert in process.env in Ihren Quellcode eingefügt wird, wenn dieser Schlüssel existiert. Es gibt potenzielle Sicherheitsprobleme, also behalten Sie das bitte im Hinterkopf.

Server-Proxy

Wie sieht Ihre bereitgestellte Webanwendung aus? Wenn sie nur JavaScript/CSS/HTML ausliefert – wobei buchstäblich alles über separate Dienste an anderer Stelle stattfindet – dann gut! Sie sind praktisch fertig. Was ich Ihnen gezeigt habe, sollte alles sein, was Sie brauchen. Der Entwicklungsserver von Vite liefert Ihre Assets nach Bedarf aus, was alle Ihre Dienste pingt, genau wie zuvor.

Aber was, wenn Ihre Web-App klein genug ist, dass Sie einige Dienste direkt auf Ihrem Webserver laufen lassen? Für das Projekt, das ich konvertiere, habe ich einen GraphQL-Endpunkt, der auf meinem Webserver läuft. Für die Entwicklung starte ich meinen Express-Server, der zuvor wusste, wie die von Webpack generierten Assets ausgeliefert werden. Ich starte auch einen Webpack-Watch-Task, um diese Assets zu generieren.

Aber da Vite seinen eigenen Dev-Server mitbringt, müssen wir diesen Express-Server starten (auf einem anderen Port als Vite) und dann Aufrufe an /graphql dorthin weiterleiten

server: {
  proxy: {
    "/graphql": "https://:3001"
  }
} 

Dies teilt Vite mit, dass alle Anfragen an /graphql an https://:3001/graphql gesendet werden sollen.

Beachten Sie, dass wir den Proxy in der Konfiguration nicht auf https://:3001/graphql setzen. Stattdessen setzen wir ihn auf https://:3001 und verlassen uns darauf, dass Vite /graphql (sowie alle Query-Argumente) an den Pfad hinzufügt.

Bibliotheken bauen

Als kurzer Bonusabschnitt besprechen wir kurz das Erstellen von Bibliotheken. Was ist zum Beispiel, wenn Sie nur eine JavaScript-Datei erstellen möchten, z. B. eine Bibliothek wie Redux. Es gibt keine zugehörige HTML-Datei, daher müssen Sie Vite zuerst mitteilen, was es erstellen soll

build: {
  outDir: "./public",
  lib: {
    entry: "./src/index.ts",
    formats: ["cjs"],
    fileName: "my-bundle.js"
  }
}

Sagen Sie Vite, wo das generierte Bundle platziert werden soll, wie es heißen soll und welche Formate erstellt werden sollen. Beachten Sie, dass ich hier CommonJS anstelle von ES-Modulen verwende, da ES-Module (zum Zeitpunkt des Schreibens) aufgrund von Bedenken, dass dies das Tree-Shaking beeinträchtigen könnte, nicht minimiert werden.

Sie würden diesen Build mit vite build ausführen. Um eine Überwachung zu starten und die Bibliothek bei Änderungen neu erstellen zu lassen, würden Sie ausführen

vite build --watch.

Zusammenfassung

Vite ist ein unglaublich aufregendes Werkzeug. Es nimmt nicht nur den Schmerz und die Tränen aus dem Bundling von Webanwendungen, sondern verbessert auch die Leistung dabei erheblich. Es wird mit einem blitzschnellen Entwicklungsserver geliefert, der Hot Module Reloading bietet und alle wichtigen JavaScript-Frameworks unterstützt. Wenn Sie Webentwicklung betreiben – sei es zum Spaß, ob es Ihr Beruf ist oder beides! – kann ich es nicht stark genug empfehlen.