Build und Deployment
Auftrag
Martina Hostettler will die App an der Mitgliederversammlung zeigen, und die Mitglieder sollen sie danach selbst ausprobieren können.
Als Vorstand möchte ich die App unter einer festen Adresse öffentlich zeigen, damit die Mitglieder sie an der Versammlung und danach ausprobieren können.
Einen Server hat der Verein noch nicht. Der Freiwillige will ausserdem, dass keine Änderung online geht, deren Tests scheitern. Die Akzeptanzkriterien lauten:
- Die App ist unter einer festen Adresse öffentlich erreichbar.
- Sie läuft ohne Kurs-API, mit Daten zum Ausprobieren.
- Jede Änderung auf
maingeht nach bestandenen Tests von selbst online.
Lernziele
Nach diesem Kapitel kannst du
- den Build einer SPA und seine Ausgabe erklären,
- den Basispfad und eine Rückfallseite für GitHub Pages einrichten,
- Umgebungsvariablen und Modi von Vite einsetzen,
- eine Attrappe der API für eine Demo verwenden,
- einen Workflow von GitHub Actions schreiben, der testet, baut und veröffentlicht.
Der Build
Abschnitt betitelt „Der Build“npm run build macht aus den Quellen den Ordner dist mit index.html und den Dateien in assets, deren Namen einen Hash tragen, wie in Kapitel 2.3. In der Fallbeispiel-App liegt der Bereich des Teams in eigenen Dateien, die erst laden, wenn jemand ihn öffnet. npm run preview zeigt den Build lokal. Laut Vite-Dokumentation dient das nur zum Prüfen und ist kein Server für den Betrieb. Ausgeliefert wird dist von einem Host für statische Seiten.
Der Basispfad
Abschnitt betitelt „Der Basispfad“Eine Projektseite auf GitHub Pages liegt unter https://name.github.io/repo/, also in einem Unterordner. Vite setzt dafür laut Dokumentation base auf /repo/, und der Router übernimmt den Pfad mit import.meta.env.BASE_URL, wie Kapitel 6.1 gezeigt hat. Statt in vite.config.js lässt sich der Pfad auch beim Bauen mit vite build --base /repo/ angeben.
Eine Rückfallseite für GitHub Pages
Abschnitt betitelt „Eine Rückfallseite für GitHub Pages“GitHub Pages kennt nur die Dateien in dist. Ruft jemand /repo/items/6 direkt auf, gibt es keine solche Datei. Laut GitHub Docs zeigt Pages dann die Datei 404.html, wenn ein Projekt eine hat. Ist sie eine Kopie von index.html, startet dort die App, und der Router zeigt die Seite der Adresse. Ein kleines Plugin für Vite legt die Kopie bei jedem Build an.
// GitHub Pages shows 404.html for every address it does not know. As a copy of index.html it starts// the app there, and the router shows the page of the address.function spaFallback() { let outDir return { name: 'spa-fallback', apply: 'build', configResolved(config) { outDir = resolve(config.root, config.build.outDir) }, closeBundle() { copyFileSync(join(outDir, 'index.html'), join(outDir, '404.html')) }, }}
export default defineConfig({ // The project page lives at https://name.github.io/lorraine-commons/ base: '/lorraine-commons/', plugins: [vue(), vueDevTools(), spaFallback()],Andere Anbieter wie Netlify oder Vercel leiten mit eigenen Regeln jede Adresse auf index.html, ihre Dokumentation beschreibt sie.
Umgebungsvariablen und Modi
Abschnitt betitelt „Umgebungsvariablen und Modi“Laut Vite-Dokumentation liest Vite Variablen aus .env, aus .env.local und aus einer Datei für den Modus, etwa .env.demo. Die Datei des Modus geht den allgemeinen Dateien vor, und Variablen, die beim Start schon in der Shell stehen, gehen allen Dateien vor. npm run dev läuft im Modus development, npm run build im Modus production, und --mode demo wählt einen eigenen. In den Code im Browser gelangen nur Variablen, die mit VITE_ beginnen, und sie stehen danach lesbar in den gebauten Dateien. Ein Schlüssel für eine API oder ein Passwort gehört darum nie in eine solche Variable.
Eine Attrappe der API für die Demo
Abschnitt betitelt „Eine Attrappe der API für die Demo“Die Demo der Fallbeispiel-App läuft im Modus demo. Seine Datei stellt zwei Variablen ein.
# The demo on the course site runs without the course API and inside a folder of the site.# npm run demo and npm run build:demo read this file through the mode demo.VITE_API=memoryVITE_HISTORY=hashVITE_API=memory gibt den Stores die Attrappe der API aus Kapitel 8.2, die Katalog, Anmeldung und Anfragen im Speicher des Browsers hält. VITE_HISTORY=hash lässt den Router den Pfad hinter # führen.
// The demo lives in a folder of the course site, whose 404 page belongs to the course. With the// address after #, the server only ever sees the folder, see .env.demo.const history = import.meta.env.VITE_HISTORY === 'hash' ? createWebHashHistory(import.meta.env.BASE_URL) : createWebHistory(import.meta.env.BASE_URL)Der Grund für den Hash-Modus ist der Ort der Demo. Sie liegt in einem Ordner der Kursseite, und eine Site auf GitHub Pages hat nur eine 404.html, die hier dem Kurs gehört. Mit dem Hash fragt der Browser den Server nur nach dem Ordner der Demo, und jede Adresse funktioniert auch beim Neuladen. Die Demo teilt sich ausserdem die Origin mit der Kursseite und damit den Speicher des Browsers. Favoriten aus den Beispielen von Kapitel 7.4 erscheinen darum auch in der Demo. Dasselbe gilt für alle Projektseiten eines Kontos auf GitHub Pages, die unter derselben Adresse name.github.io liegen. Schlüssel im localStorage brauchen darum einen Namen, der zur App gehört.
Eine andere Art von Attrappe fängt Anfragen im Netz ab statt im Code. Mock Service Worker installiert dafür einen Service Worker, und die App ruft weiter fetch auf. Die Fallbeispiel-App braucht das nicht, weil ihre Stores die API schon über einen Adapter bekommen.
Ein Workflow von GitHub Actions
Abschnitt betitelt „Ein Workflow von GitHub Actions“Ein Workflow beschreibt in einer Datei unter .github/workflows, was GitHub nach einem Ereignis wie einem Push tun soll. Das Beispiel der Vite-Dokumentation und die GitHub Docs verwenden dafür die Aktionen configure-pages, upload-pages-artifact und deploy-pages.
name: Deploy
on: push: branches: [main] workflow_dispatch:
permissions: contents: read pages: write id-token: write
# One deployment at a time, a newer push waits for the one that runsconcurrency: group: pages cancel-in-progress: false
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: lts/* cache: npm - run: npm ci # A failing test stops the workflow before anything goes online - run: npm test - run: npm run build - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: path: dist
deploy: needs: build runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - id: deployment uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1Der Job build testet und baut, der Job deploy veröffentlicht erst danach. Scheitert ein Test, endet build, und deploy startet nicht. Damit der Workflow veröffentlichen darf, braucht er die Rechte pages: write und id-token: write, und unter «Settings» und «Pages» steht als Quelle «GitHub Actions». Die Aktionen sind hier wie im Kursrepo auf einen Commit festgelegt, der Kommentar nennt die Version. Laut GitHub Docs ist eine Seite auf Pages öffentlich, auch wenn das Repository privat ist.
Die Demo des Kurses
Abschnitt betitelt „Die Demo des Kurses“Die Kursseite veröffentlicht die Demo selbst. Das Skript scripts/build_demo.mjs im Kursrepo baut die Fallbeispiel-App im Modus demo in beiden Sprachen in den Ordner der Kursseite, und der Workflow des Kurses veröffentlicht sie zusammen mit den Seiten. Die Demo der Fallbeispiel-App läuft ohne Server, Anmeldung und Anfragen bleiben bis zum Schliessen des Tabs erhalten.
In deinem Projekt
Abschnitt betitelt „In deinem Projekt“Der Mindestumfang verlangt ein öffentliches Deployment. Setz den Basispfad, lege 404.html an, wenn deine App den History-Modus verwendet, und schreib den Workflow, sobald die erste Seite steht, nicht erst vor der Abgabe. Beschreib im README, wie die App gebaut und veröffentlicht wird, und leg für jede Umgebungsvariable eine Zeile in .env.example an, ohne Geheimnisse.
Im Code
Abschnitt betitelt „Im Code“Arbeite mit der Fallbeispiel-App im Kursrepo im Ordner case/08-architecture/de. Schreib auf, was du erwartest, bevor du etwas ausführst.
- Bau die App mit
npm run buildund öffnedist/index.html. Mit welchem Pfad beginnen die Verweise auf die Dateien? - Bau sie noch einmal mit
npx vite build --base /lorraine-commons/. Was ändert sich indist/index.html? - Starte die Demo mit
npm run demound öffne ein Ding. Wie sieht die Adresse aus, und warum? - Schreib
VITE_API=memoryin eine Datei.env.localund startenpm run dev. Was ändert sich, was bleibt gleich? Lösch die Datei danach wieder.
Aufgaben
Abschnitt betitelt „Aufgaben“Die Aufgaben machen eine kleine App mit Router bereit für GitHub Pages. Du findest das Projekt im Kursrepo im Ordner exercises/08-architecture/deployment/de. Die Tests der ersten beiden Stufen bauen das Projekt in einen temporären Ordner, das dauert ein paar Sekunden.
npm installnpm testAufgabe Einstieg
Die App soll unter https://name.github.io/lorraine-commons/ laufen. Setz in vite.config.js den Basispfad und prüf nach npm run build die Pfade in dist/index.html.
Lösung
export default defineConfig({ // The project page lives at https://name.github.io/lorraine-commons/ base: '/lorraine-commons/', plugins: [vue(), vueDevTools(), spaFallback()],Der Router liest den Pfad mit import.meta.env.BASE_URL und braucht keine eigene Änderung.
Aufgabe Vertiefung
Ein direkter Link auf /lorraine-commons/items/6 landet auf der Seite für Fehler von GitHub Pages. Schreib in vite.config.js ein Plugin, das nach jedem Build dist/index.html nach dist/404.html kopiert.
Lösung
// GitHub Pages shows 404.html for every address it does not know. As a copy of index.html it starts// the app there, and the router shows the page of the address.function spaFallback() { let outDir return { name: 'spa-fallback', apply: 'build', configResolved(config) { outDir = resolve(config.root, config.build.outDir) }, closeBundle() { copyFileSync(join(outDir, 'index.html'), join(outDir, '404.html')) }, }}apply: 'build' beschränkt das Plugin auf den Build. configResolved merkt sich den Ordner der Ausgabe, auch wenn jemand ihn mit --outDir ändert.
Aufgabe Knobelei
Schreib den Workflow .github/workflows/deploy.yml. Er startet bei jedem Push auf main, installiert mit npm ci, testet mit npm test, baut mit npm run build, lädt dist hoch und veröffentlicht in einem eigenen Job auf GitHub Pages.
Lösung
Die Lösung steht oben im Abschnitt über den Workflow. Er läuft erst, wenn das Projekt in einem eigenen Repository auf GitHub liegt und unter «Settings» und «Pages» die Quelle «GitHub Actions» gewählt ist.
Typische Fehler
Abschnitt betitelt „Typische Fehler“| Fehler | Was hilft |
|---|---|
| Die veröffentlichte App zeigt eine leere Seite. | base fehlt oder passt nicht zum Namen des Repositorys. |
| Ein direkter Link auf eine Seite zeigt die Fehlerseite von GitHub Pages. | 404.html als Kopie von index.html mitliefern. |
Ein Schlüssel für eine API steht in einer Variable mit VITE_. | Alles mit VITE_ landet lesbar im Code des Browsers. Geheimnisse gehören auf einen Server. |
| Der Workflow veröffentlicht, obwohl Tests scheitern. | npm test vor dem Build und das Veröffentlichen in einem Job, der auf den Build wartet. |
| Der Workflow scheitert, weil Rechte fehlen. | pages: write und id-token: write setzen und als Quelle von Pages «GitHub Actions» wählen. |
Ein KI-Assistent schlägt Vue CLI oder vite preview als Server für die Veröffentlichung vor. | Vue CLI ist laut seiner Dokumentation im Wartungsmodus, und vite preview ist kein Server für den Betrieb. Gebaut wird mit vite build, ausgeliefert von einem Host für statische Seiten. |
Selbstcheck
Abschnitt betitelt „Selbstcheck“Selbstcheck
- Warum braucht eine Projektseite auf GitHub Pages einen Basispfad?
- Wozu dient
404.html, und warum genügt eine Kopie vonindex.html? - Welche Datei liest
vite build --mode demo, und welche Variablen gelangen in den Browser? - Warum veröffentlicht der Workflow nichts, wenn ein Test scheitert?
Antworten
- Sie liegt in einem Unterordner. Ohne Basispfad sucht der Browser die Dateien der App ab der Wurzel der Domain.
- Pages zeigt sie für jede unbekannte Adresse. Als Kopie von
index.htmlstartet dort die App, und der Router zeigt die Seite der Adresse. .env.demo, dazu.envund.env.local. In den Browser gelangen nur Variablen, die mitVITE_beginnen.- Die Tests laufen im Job
build. Scheitert einer, endet der Job, und der Jobdeploywartet vergeblich auf ihn.
Auftrag
Die Demo läuft an der Mitgliederversammlung, und Mitglieder probieren sie danach zu Hause aus. Die neue Freiwillige hat inzwischen einen KI-Assistenten um eine Liste der überfälligen Ausleihen für das Team gebeten. Der Code sieht vernünftig aus, die Listen bleiben aber leer, und die Konsole meldet Fehler. Das nächste Kapitel zeigt, wie du solchen Code prüfst.
Texte und Abbildungen stehen unter CC BY-SA 4.0, der Code unter der MIT-Lizenz und die Daten des Kurses unter CC0 1.0. Die Schrift der Abbildungen ist Kalam unter der SIL Open Font License 1.1. Die Quellen liegen auf GitHub.