Zum Inhalt
wannacode

Von der Sandbox zum Projekt

Kurz gesagt: Vite macht aus den Komponenten ein echtes Projekt: npm create vite legt die Ordner an, npm run build baut dist/ zum Veröffentlichen zusammen, und VITE_-Umgebungsvariablen werden in den Code der Seite eingebaut, sind also öffentlich.

Problem

In der Sandbox läuft es, im Browser nicht

Dein Shop läuft in der Sandbox. Du kopierst die Dateien in einen Ordner, stellst ihn bereit und öffnest die Seite. Sie ist leer, und die Konsole sagt:

Uncaught SyntaxError: Unexpected token '<'

Der Browser versteht JavaScript, und <App /> ist JSX. Selbst ohne JSX folgt ein weiterer Fehler: import { createRoot } from 'react-dom/client' ist kein Pfad zu einer Datei, und der Browser weiß nicht, wo react-dom liegt.

Die Sandbox in den Lektionen hat dir diese Arbeit abgenommen. Sie übersetzt JSX auf der Lektionsseite in JavaScript und weiß, woher sie die Bibliothek holt. In einem echten Projekt macht das ein Werkzeug.

Die Idee

Vite: ein Entwicklungsserver und ein Build

Vite ist das Werkzeug, mit dem die meisten neuen React- und Vue-Projekte angelegt werden. Ein leeres Projekt kostet einen Befehl:

Terminal
npm create vite@latest shop -- --template react
cd shop
npm install
npm run dev

--template wählt die Vorlage: react oder react-ts (mit TypeScript). Die zwei Striche -- davor sagen npm, dass die Option für create-vite gilt und nicht für npm selbst. npm install lädt die Bibliotheken in den Ordner node_modules.

Vite hat zwei Aufgaben, und package.json führt sie als Skripte auf:

  • npm run dev startet einen Entwicklungsserver unter http://localhost:5173. Er übersetzt jede Datei, wenn der Browser sie anfordert. Speicherst du eine Datei, aktualisiert sich die Seite sofort.
  • npm run build erzeugt die fertige Seite in einem Ordner dist/. Das ist der Build: alles übersetzt und zu einfachen Dateien zusammengefügt.
  • npm run preview liefert dist/ unter http://localhost:4173 aus, damit du den Build ansehen kannst, bevor du ihn veröffentlichst.

Die Idee

Die Ordner und die Regeln

Die Vorlage gibt dir wenig. So sieht derselbe Shop aus, wenn er wächst:

shop/
index.html der Einstieg: <script type="module" src="/src/main.jsx">
package.json die Bibliotheken und die Skripte dev, build, preview
vite.config.js die Einstellungen von Vite (das React-Plugin)
public/ Dateien, die unverändert kopiert werden (favicon.svg)
src/
main.jsx createRoot(…).render(<App />)
App.jsx
components/ ProductCard.jsx, CartSummary.jsx
pages/ CatalogPage.jsx, ProductPage.jsx (für den Router)
context/ CartContext.jsx (der gemeinsame Zustand)
api/ products.js: die Funktionen mit fetch
assets/ Bilder, die der Code mit import einbindet

index.html, public/ und src/main gehören zu Vite. Die anderen Ordner sind eine Absprache im Team, keine Regel: Vite braucht sie nicht. Die Absprache hilft dir, Dinge ohne Suchen zu finden. Die Tests aus der vorigen Lektion liegen neben der Komponente: ProductCard.test.<Only when="react">jsx</Only><Only when="vue">js</Only>.

Linting und Formatierung. Ein Linter liest den Code und findet Fehler, bevor du ihn ausführst: einen Hook in einem if, eine vergessene Variable. Formatierung (zum Beispiel mit Prettier) setzt Leerzeichen und Anführungszeichen im ganzen Team gleich, damit sich im Code-Review niemand darüber streitet. Die React-Vorlage bringt einen Linter mit: npm run lint startet oxlint mit der Regel für Hooks.

Sieh es dir an

Was der Build tut

Wie aus ein paar Dateien mit JSX und import eine Seite wird, die der Browser versteht.

import { createRoot } from 'react-dom/client';
import App from './App.jsx';
import './index.css';
createRoot(document.getElementById('root')).render(<App />);
// App.jsx
import ProductCard from './components/ProductCard.jsx';
import hero from './assets/hero.png';
// npm run build → dist/
// dist/index.html
// dist/assets/index-[hash].js
// dist/assets/index-[hash].css
// dist/assets/hero-[hash].png
Schritt für Schritt, in Worten
  1. Der Build beginnt bei index.html. Die Datei lädt main.jsx mit <script type="module">, und dort beginnt die App.
  2. Vite folgt jedem import, Datei für Datei: App, ProductCard, dem CSS, dem Bild. Eine Datei, die niemand importiert, kommt nicht hinein.
  3. Das ganze JavaScript, samt der Bibliothek aus node_modules, wird zu einer Datei zusammengefügt und verkleinert: ohne Leerzeichen, mit kurzen Namen.
  4. Heraus kommen einfache Dateien, die der Browser versteht: eine .js, eine .css und das Bild. Jeder Name trägt einen Hash, der aus dem Inhalt berechnet wird.
  5. dist/index.html zeigt jetzt auf die neuen Namen. Der ganze Ordner dist/ ist die Seite: Du veröffentlichst ihn, wie er ist.
  6. Du änderst ProductCard und baust noch einmal. Das JavaScript ist anders, also ist sein Name neu. Das CSS hat sich nicht geändert und behält seinen Namen.
  7. Deshalb darf der Browser eine Datei mit Hash für immer behalten: Eine neue Version hat immer einen neuen Namen. Besucher laden nur herunter, was sich geändert hat.

dist/ ist die fertige Seite: eine HTML-Datei und ein Ordner assets/. Sie braucht keinen Server, nur einen Host, der Dateien ausliefert. Sie liegt nicht in Git (die Vorlage trägt sie in .gitignore ein): Du baust sie für jedes Release neu.

Die Idee

TypeScript in Komponenten

In der Sandbox kannst du ProductCard alles übergeben: product={42}. Der Fehler zeigt sich erst beim Zeichnen. TypeScript sagt, was die Komponente annimmt, und findet den Fehler im Editor, bevor du etwas ausführst:

ProductCard.tsx
type Product = { id: number; name: string; price: number };
type Props = {
product: Product;
onAdd: (product: Product) => void;
};
export default function ProductCard({ product, onAdd }: Props) {
// …
}

Die Datei ist .tsx. Wenn App onAdd vergisst, sagt der Editor Property 'onAdd' is missing in type … but required in type 'Props'.

Eines überrascht viele: Vite entfernt die Typen nur, es prüft sie nicht. Das hält es schnell. Die Prüfung läuft getrennt: Im TypeScript-Template ist npm run build tsc -b && vite build. Stimmen die Typen nicht, hält der Build an, bevor Vite startet.

Probier es aus

Finde den Fehler

Der Editor unterstreicht eine Zeile rot. Welche?

Wähle die Zeile mit dem Fehler.

Die Idee

Umgebungsvariablen sind öffentlich

Die Adresse der API ist auf deinem Computer und auf der echten Seite verschieden. Deshalb steht sie nicht im Code, sondern in einer Datei .env im Wurzelordner des Projekts:

Terminal
VITE_API_URL=https://api.shop.example
const API_URL = import.meta.env.VITE_API_URL;

Vite gibt dem Code nur die Variablen, die mit VITE_ beginnen. Bei npm run build schreibt es den Wert ins JavaScript: dist/assets/index-….js enthält https://api.shop.example als normalen Text. Jeder, der die Seite öffnet, kann ihn in den Werkzeugen des Browsers lesen.

Deshalb legst du nie ein Geheimnis in eine VITE_-Variable: einen Zahlungsschlüssel, ein Datenbankpasswort, einen API-Schlüssel, der Geld kostet. Dass .env nicht in Git liegt, spielt keine Rolle. Das Geheimnis steckt im Bundle. Geheimnisse leben auf dem Server, und die Seite schickt eine Anfrage an deine API (die Lektion über Geheimnisse in „Sicherheit für Programmierer“).

Probier es aus

Finde den Fehler

Die Funktion bezahlt eine Bestellung. Welche Zeile lässt ein Geheimnis in die Welt?

Wähle die Zeile mit dem Fehler.

Die Idee

Veröffentlichen und die Adressen des Routers

dist/ sind nur Dateien, also genügt jeder Host für statische Seiten. Veröffentlichen heißt: npm run build und dist/ kopieren. Viele Hosts übernehmen das bei jedem Push nach Git für dich.

Ein Detail aus der Routing-Lektion fehlt noch. Die echte Seite benutzt BrowserRouter, und die Adressen sehen aus wie /product/7. Ein Kunde öffnet so eine Adresse direkt (über einen Link in einer E-Mail oder mit einem Neuladen). Der Browser fragt den Host nach /product/7, und dist/ hat keine solche Datei: nur index.html und assets/. Der Host sagt 404.

Die Lösung ist eine Regel auf dem Host: Jede Adresse, die keine Datei ist, liefert index.html. Die App startet, und der Router liest die Adresse und zeichnet ProductPage. Das heißt SPA-Fallback. Jeder Host schreibt es auf seine Weise, zum Beispiel in nginx:

location / {
try_files $uri $uri/ /index.html;
}

npm run dev und npm run preview machen das von selbst, deshalb zeigt sich der Fehler erst auf dem echten Host. Prüf es gleich nach dem ersten Release: Öffne eine Produktadresse direkt.

Die Idee

Der nächste Schritt: Next.js und Nuxt

Alles in diesem Kurs ist eine App im Browser: Der Server liefert eine leere index.html und JavaScript, und die Seite wird beim Kunden gezeichnet. Bis das JavaScript ankommt und startet, sieht der Kunde nichts.

Rendering auf dem Server (Server-Side Rendering, SSR) zeichnet das erste HTML auf dem Server. Die Seite erscheint früher, und Suchmaschinen lesen sie ohne JavaScript. Danach wachen dieselben Komponenten im Browser auf und werden interaktiv. Das tun die Frameworks über den Bibliotheken: Next.js. Sie bringen auch Routing über Dateien mit und Code, der nur auf dem Server läuft (dort können Geheimnisse liegen).

Komponenten, Props, Zustand und Effekte sind dieselben. Dieser Kurs ist also die Grundlage, und das Framework ist die nächste Lektion, die du dir selbst beibringst.

Genauer, für Neugierige

Der Entwicklungsserver bündelt nicht. Bei npm run dev lädt der Browser jedes Modul einzeln, und Vite übersetzt es auf Anfrage. Deshalb startet er auch bei einem großen Projekt sofort. Das Zusammenfügen zu einer Datei passiert erst im Build.

Kleine Bilder werden eingebettet. Ein Bild unter 4 KB, das du mit import einbindest, wird keine Datei: Vite legt es als data:-Adresse ins JavaScript (die Einstellung heißt build.assetsInlineLimit). Dateien aus public/ werden nie angefasst und bekommen keinen Hash, deshalb verweist du mit dem vollen Pfad auf sie: /favicon.svg.

Die .env-Dateien. .env.local gilt nur für deinen Computer (die Vorlage hält sie mit *.local aus Git heraus). .env.production gilt für den Build und .env.development für npm run dev. import.meta.env.DEV und import.meta.env.PROD sagen, in welchem Modus der Code läuft. Alle Werte sind Text: VITE_PAGE_SIZE=20 ergibt '20', nicht 20.

Eine Seite in einem Unterordner. Wenn die Seite unter https://example.com/shop/ liegt, muss Vite das wissen: base: '/shop/' in vite.config.js. Sonst zeigt index.html auf /assets/… im Wurzelordner, und die Dateien werden nicht gefunden.

npm run preview ist nicht für die echte Seite. Es ist dafür da, den Build auf deinem Rechner zu prüfen. Die echte Seite liefert ein Host oder ein Server wie nginx aus.

StrictMode in main.jsx. Die Vorlage packt <App /> in <StrictMode>. In der Entwicklung rendert es jede Komponente zweimal und führt die Effekte aus, räumt sie auf und führt sie wieder aus, um Effekte ohne Aufräumen aufzudecken. Der Build tut das nicht.

Übung

Vom leeren Ordner zum Build

Bring die Befehle in die richtige Reihenfolge: ein neues Projekt, die Bibliotheken, der Entwicklungsserver, der Build und die Prüfung des Builds.

  1. npm run build
  2. cd shop
  3. npm run dev
  4. npm create vite@latest shop -- --template react
  5. npm install
  6. npm run preview

Test

Teste dich

1. Was macht npm run build in einem Vite-Projekt?
2. Warum haben die Namen in dist/assets/ einen Hash (index-DSXtc1GT.js)?
3. Der Zahlungsschlüssel steht in VITE_PAYMENT_KEY in .env, und .env liegt nicht in Git. Ist er geheim?
4. Die Seite ist veröffentlicht. Ein Kunde öffnet /product/7 direkt und bekommt vom Host ein 404. Was fehlt?
5. Das Projekt benutzt TypeScript. vite build läuft durch, und npm run build bricht mit einem Typfehler ab. Warum?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·