10: Veröffentlichung & Hosting
Ihr baut jetzt also eine App – aber was, wenn sie dann endlich einmal fertig ist? Darum geht es in dieser Session.
Inhalt
Wie geht man mit PWA-Events richtig um und welche Optionen habe ich im Manifest?
Wir haben während des Kurses ganz oft davon gesprochen, dass wir eine PWA
entwickeln, aber eure Apps werden erst dann zu PWAs, wenn sie „verpackt“ werden.
Das geschieht vollautomatisch durch das cli-plugin-pwa
, das installiert wurde
als wir während des Setups unseres Projekts mit der Vue CLI die Option „PWA
Support“ ausgewählt haben. Um unsere PWA-Funktionalität testen zu können, müssen
wir unsere Apps also erst einmal mit npm run build
verpacken und dann über
einen lokalen Webserver laufen lassen – mehr dazu im nächsten Abschnitt.
Jetzt soll es erst einmal darum gehen, welche Optionen ihr dem PWA-Plugin übergeben könnt, und welche Dinge ihr außerdem beachten solltet, um euren Nutzern eine gute Erfahrung zu bieten. Eine genaue Beschreibung aller Optionen findet ihr in der Dokumentation des Plugins. Ich werde hier nur auf die für den Anfang wichtigsten eingehen.
Konfigurationsoptionen
All diese Optionen könnt ihr in der Datei vue.config.js
in eurem Projektordner
unter der Eigenschaft pwa
eintragen – müsst es aber nicht, denn sie haben alle
Standardeigenschaften. Verwendet also nur die Optionen, die ihr ändern möchtet.
appleMobileWebAppCapable
ist standardmäßig deaktiviert, da iOS <11.3 keine PWA-Unterstützung bietet. Wenn ihr diese Option auf'yes'
stellt, wird eure App auch auf iOS wie eine PWA behandelt, aber es könnte zu Problemen führen, wenn eine alte iOS-Version verwendet wird. Mehr dazu hier.appleMobileWebAppStatusbarStyle
hat standardmäßig den Wert'default'
. Diese Eigenschaft gibt an, wie mit der iOS-Statusleiste umgegangen werden soll.'default'
ergibt eine weiße Statusleiste mit schwarzem Text.'black'
lässt die Statusleiste wie einen komplett schwarzen Balken erscheinen (ohne Text) und'black-translucent'
lässt die Statusleiste halbtranparent als Overlay über eurer App erscheinen – sorgt also dafür, dass ihr oben etwas „Fleisch“ (padding) habt, wenn ihr diese Option verwendet!manifestOptions
ist ein Objekt, in dem ihr die Optionen für euer Web App Manifest ablegen könnt. Das Manifest ist neben dem Service Worker das Herzstück eurer PWA, ohne es ist sie nicht installierbar. Das Manifest wird ebenfalls für euch generiert, in dieser Option könnt ihr die folgenden Standardwerte verändern (in Klammern angegeben):name
(pwa.name): der Name eurer App, der beim Starten und unter dem Icon angezeigt wirdshort_name
(pwa.name): ein verkürzter Name, der angezeigt wird, wenn nicht genug Platz fürname
da istdescription
: hier könnt ihr eine kurze Beschreibung eurer App ablegenstart_url
('.'
): die URL auf der eure App starten soll (sollte immer auf'/'
gesetzt werden, da es ansonsten zu 404-Fehlern kommen kann)display
('standalone'
): dieser Wert gibt an, wie die App sich verhalten soll. Mit'standalone'
wird sie sich wie eine normale App verhalten, aber es gibt z.B. noch'fullscreen'
, wenn die App den gesamten Bildschirm einnehmen soll, oder'browser'
, wenn man die Schaltflächen des Browsers sehen können soll. Meistens ist'standalone'
aber durchaus in Ordnungtheme_color
(pwa.themeColor): die Farbe, die die Statusleiste der App auf Android haben sollbackground_color
: die Hintergrundfarbe, die der Startbildschirm eurer App haben soll (wenn die App gestartet und nur das Icon und der Name angezeigt werden während sie lädt)orientation
: wenn ihr hier'portrait'
angebt, wird sich eure App nicht automatisch drehen, wenn ihr euer Smartphone dreht
msTileColor
gibt an, welche Farbe die Kachel eurer App im Windows-Startmenü haben sollname
ist der Name eurer App, der auch im Manifest benutzt wird, solltet ihr in den manifestOptions keinen Anderen angeben. Er ist standardmäßig auf den Wert der „name“ Eigenschaft in eurerpackage.json
eingestelltthemeColor
ist die Farbe, die auch im Manifest benutzt wird um die Fensterfarbe, bzw. die Farbe der Statusleiste unter Android festzulegen. Hier bietet es sich an die Primärfarbe eurer App, oder einen leicht dunkleren Ton davon zu benutzenworkboxOptions
ist ein Objekt, in das ihr Optionen für die Generierung eures Service Workers eingeben könnt. Was genau das ist ist zu kompliziert für diesen Kurs, aber wenn ihr dort die EigenschaftskipWaiting
auftrue
stellt, könnt ihr die App auf eine neue Version aktualisieren, ohne sie neu starten zu müssen, weshalb ich euch das stark empfehle.
Tipp:
Wie gesagt: ihr müsst nur die Optionen verwenden, die ihr ändern wollt. Ich
empfehle euch, sie einfach alle mal auszuprobieren und zu sehen, was sich
ändert. Persönlich passe ich meistens nur die Farben und den Namen an und
setze skipWaiting
auf true
– für den Rest reichen
die Standardwerte vollkommen aus.
Service Worker Events
Der Service Worker ist ein Skript, welches euch das cli-plugin-pwa
generiert
und das sich darum kümmert, dass eure App offline laufen und installiert werden
kann. Das ist natürlich nur eine sehr grobe Beschreibung, denn Service Worker
können noch viel mehr, aber für diesen Kurs ist das zunächst alles, was ihr
wissen müsst. Mehr Infos gibt es hier,
aber wie gesagt, das ist für unsere Zwecke nicht weiter relevant.
Der Service Worker gibt eine Reihe von Events aus, auf die ihr reagieren könnt,
um zum Beispiel in der App anzuzeigen, wenn die Internetverbindung abbricht,
oder es ein Update für die App gibt. Die Vue CLI macht uns auch das leicht und
generiert uns die Datei src/registerServiceWorker.js
in der wir einfach unsere
Funktionen für das jeweilige Event schreiben können.
Hier bietet es sich zum Beispiel an über Vuex mit dem Rest eurer App zu kommunizieren. Hier ist meine Version der Datei für Done in Time:
/* eslint-disable no-console */
import { register } from 'register-service-worker';
// importiere den Vuex Store, um mit dem Rest der App zu kommunizieren
import Store from '@/store';
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready() {
if (window.location.host.includes('localhost') || window.location.host.includes('172.0.0.1')) {
console.log(
'App is being served from cache by a service worker.\n'
+ 'For more details, visit https://goo.gl/AFskqB',
);
}
},
registered() {
if (window.location.host.includes('localhost') || window.location.host.includes('172.0.0.1')) console.log('Service worker has been registered.');
},
// diese Funktion wird aufgerufen, sobald der Service Worker initialisiert hat
// und die App offline verfügbar ist
cached() {
if (window.location.host.includes('localhost') || window.location.host.includes('172.0.0.1')) console.log('Content has been cached for offline use.');
// über die addToast-Mutation kann ich in MEINER App Benachrichtigungen anzeigen,
// ihr könnt euch auch eine solche Funktionalität einbauen
Store.commit('addToast', { message: 'Done in Time has been fully cached on your device and will be available offline from now on', type: 'positive' });
},
updatefound() {
if (window.location.host.includes('localhost') || window.location.host.includes('172.0.0.1')) console.log('New content is downloading.');
},
// diese Funktion wird aufgerufen sobald ein Update für eure App heruntergeladen wurde
async updated(registration) {
if (window.location.host.includes('localhost') || window.location.host.includes('172.0.0.1')) console.log('New content is available; please refresh.');
// ich speichere hier in der Datenbank, dass der Changelog angezeigt werden soll
await Store.dispatch('setUserProperty', { key: 'forceChangelog', value: true });
// und sende dem Nutzer dann eine Benachrichtigung, dass er die App nur neu laden
// muss indem er einen Button klickt, um die neue Version zu verwenden
// das funktioniert NUR, wenn die option skipWaiting in den workboxOptions auf true gesetzt ist!
// Ansonsten wird die App erst aktualisiert, wenn sie komplett geschlossen und neu geöffnet wird
Store.commit('addToast', {
action: () => {
if (registration.waiting) registration.waiting.postMessage({ type: 'SKIP_WAITING' });
window.location.reload(true);
},
actionLabel: 'Refresh',
message: 'A new version of Done in Time is available, refresh to use the newest version.',
timeout: -1,
});
},
offline() {
if (window.location.host.includes('localhost') || window.location.host.includes('172.0.0.1')) console.log('No internet connection found. App is running in offline mode.');
},
error(error) {
if (window.location.host.includes('localhost') || window.location.host.includes('172.0.0.1')) console.error('Error during service worker registration:', error);
Store.commit('addToast', { message: `There was an error registering the service worker: ${error.message}` });
},
});
}
Wie ihr seht, benutze ich eigentlich nur die cached()
und updated()
Funktionen, die anderen geben mir nur Statusupdates, so lange die App lokal auf
meinem PC (unter „localhost“) läuft. Was ich hier mache ist komplett optional,
verbessert aber die Nutzererfahrung denke ich sehr, da der Nutzer bescheid weiß,
was gerade im Hintergrund passiert.
Tipp:
Baut diese Funktionalität ruhig ein, wenn ihr möchtet, aber schaut, dass vorher der Rest eurer App gut funktioniert!
Das Install-Prompt
Sobald der Browser merkt, dass sich eure App installieren lässt (sofern der
Browser das unterstützt), sendet er das beforeinstallprompt
-Event. Wenn ihr
dieses Event nicht abfangt, sehen eure Nutzer sofort ein pop-up Prompt, das sie
dazu bewegen soll, eure App zu installieren. Das ist aus zwei Gründen
problematisch:
- Das Prompt kommt vom Browser, passt also überhaupt nicht zur Gestaltung eurer App
- Das Prompt kommt meistens innerhalb weniger Sekunden nachdem euer Nutzer die App zum ersten Mal aufgerufen hat und womöglich noch gar nicht weiß, ob er/sie die App überhaupt mag
Wenn ihr also mit diesem Prompt nicht richtig umgeht ist die Chance hoch, dass euer Nutzer es einfach wegklickt, oder ablehnt und somit die Möglichkeit verliert, eure App zu installieren, falls er oder sie eure App dann überhaupt noch verwenden möchte.
Deshalb bietet es sich an, im created()
-Lifecycle-Hook eurer App.vue
das
Event abzufangen und für später abzuspeichern, sodass ihr es dem Nutzer zu einem
geeigneteren Zeitpunkt wieder anzeigen könnt:
export default {
// …andere Optionen…
created() {
// registriere einen Event Listener für das beforeinstallprompt event
window.addEventListener('beforeinstallprompt', (prompt) => {
prompt.preventDefault(); // fange das Event ab und verhindere seinen Effekt
// speichere das Event im Vuex Store, damit es zu einem späteren Zeitpunkt
// aufgerufen werden kann, dafür MUSS natürlich im Store eine
// entsprechende Mutation registriert sein!
this.$store.commit('setInstallPrompt', prompt);
});
},
watch: {
// registriere eine Funktion, die immer dann ausgeführt wird, wenn die Route
// sich verändert
$route(to, from) {
// hier überprüfe ich, ob die letzte Route nicht das Onboarding war und ob
// ein installPrompt im Vuex Store hinterlegt wurde und der Nutzer noch
// nicht gefragt wurde, ob er die App installieren möchte
// WICHTIG: dieses Skript funktioniert natürlich NUR, wenn ihr euren Store
// so aufbaut wie ich es getan habe!!! Seht es als Inspiration, es ist
// KEINE KOMPLETTLÖSUNG
if (
from.name !== 'Onboarding'
&& this.$store.state.application.installPrompt
&& !this.$store.state.user.prompted
) {
// zeige eine Notification in der App, die darauf hinweist, dass sie installiert werden kann
this.$store.commit('addToast', {
// wenn auf den "Install"-Button der Notification geklickt wird:
action: async () => {
// benutze das abgespeicherte Prompt, um das Pop-Up des Browsers anzuzeigen
this.$store.state.application.installPrompt.prompt();
// userChoice ist eine Promise, die angibt, wie der Nutzer auf das Pop-Up reagiert
const choice = await this.$store.state.application.installPrompt.userChoice;
// wenn es akzeptiert wurde:
if (choice.outcome === 'accepted') {
this.$store.commit('addToast', {
message: 'Great! Done in Time should have been added to your homescreen or desktop. You can now launch it from there and close this tab.'
});
} else { // wenn nicht
this.$store.commit('addToast', { message: 'Alright, we won’t bother you again.' });
}
},
actionLabel: 'Install',
message: 'You seem to be enjoying Done in Time! Would you like to install it to your device?',
timeout: -1,
});
// speichere ab, dass der Nutzer gefragt wurde, ob die App installiert werden soll
// damit er nicht ständig damit genervt wird
await this.$store.dispatch('setUserProperty', { key: 'prompted', value: true });
}
}
}
// …andere Optionen…
};
Wie auch die Reaktion auf die Service Worker Events ist dieser Teil optional, das bedeutet, ihr müsst das Event in eurer App nicht behandeln, um den Kurs zu bestehen (aber natürlich verbessert es eure Note 😉). Falls ihr aber jemals eine professionelle App baut, solltet ihr es auf jeden Fall tun, denn alles Andere ist eine sehr schlechte UX.
Auch ohne diese Optionen und speziellen Funktionen erhaltet ihr nach dem Verpacken eurer App eine voll funktionsfähige PWA – dank der Vue CLI. Aber jetzt habt ihr zumindest einen Überblick darüber, wie ihr das automatisch generierte Manifest und den Service Worker beeinflussen könnt.
npm run build
?
Was passiert bei Ich habe jetzt schon mehrmals angemerkt, dass eure App „verpackt“ werden muss,
bevor ihr sie auf einen Webserver laden und somit der Welt zugänglich machen
könnt. Dieses „Verpacken“ nennt man auf Englisch „build“, also „bauen“. In eurem
Projekt, das ihr mit der Vue CLI erstellt habt, könnt ihr einfach npm run build
in einem Terminal aufrufen und die Vue CLI kümmert sich automatisch um alles.
Dieser Prozess kann eine Weile dauern, aber am Ende erhaltet ihr einen Ordner
(standardmäßig dist
) in eurem Projektordner, den ihr eins zu eins so auf euren
Webspace laden könnt. In diesem Ordner befindet sich eine index.html
-Datei,
die eure Vue-Anwendung lädt und initialisiert. Mehr braucht ihr theoretisch
nicht zu wissen.
Achtung:
Wenn eure App nicht direkt unter der Domain (also z.B. https://meine-app.de)
läuft, sondern in einem Unterordner (z.B. https://meine-website.de/meine-app)
müsst ihr in eurer vue.config.js
den richtigen
Pfad unter der Eigenschaft publicPath
ablegen! Mehr Informationen
dazu findet ihr hier.
Falls ihr aber neugierig seid, hier ein grober Überblick, was genau passiert, wenn ihr dieses Kommando ausführt:
- Euer JavaScript wird so transpiliert, dass es auch ältere Browser verstehen
- Falls ihr einen CSS-Preprocessor benutzt, wird dieser in normales CSS übersetzt
- Euer CSS wird mit den korrekten Prefixen für ältere Browser versehen (z.B. -webkit-transition, -moz-transition, etc.)
- Statische Assets wie Bilder und Icons werden richtig verlinkt und an die relevanten Stellen kopiert
- Alle externen Libraries, die ihr über
npm
installiert habt werden zusammengefasst und komprimiert - All euer Code wird komprimiert und in kleinere Dateien aufgeteilt, externe Libraries und Skripte werden korrekt importiert
- Ein Web App Manifest und ein Service Worker werden generiert und verlinkt
- Eine
index.html
wird generiert, die automatisch die richtigen Stylesheets und Skripte importiert - Alles wird in einen Ordner gepackt (normalerweise
dist
), den ihr einfach auf euren Server kopieren könnt, um ihn zu veröffentlichen - Ihr bekommt Warnungen angezeigt, wenn eure Dateien zum Beispiel zu groß sind, oder es Fehler in euren Skripten gibt
Wenn ihr einmal mehr Erfahrung gesammelt habt, könnt ihr euch diesen Prozess genauer ansehen und in bestimmte Schritte eingreifen, oder sie erweitern, um zum Beispiel Bilder auf die richtigen Größen zuzuschneiden, etc. Möglich wird das Ganze durch Technologien wie Webpack und Babel.
Achtung:
Zwar könnt ihr euren dist-Ordner problemlos auf eurem Webspace veröffentlichen, aber es gibt einige wichtige Dinge zu beachten, wenn ihr wollt, dass alles richtig funktioniert, z.B. müssen bestimmte Weiterleitungen eingerichtet werden und ihr braucht ein SSL-Zertifikat (https), damit eure PWA funktionieren kann. Diese Schritte weichen stark von Hoster zu Hoster ab, aber in diesem Abschnitt der Vue CLI Dokumentation findet ihr Anleitungen und Hinweise für sehr viele von ihnen.
Was ist CI/CD?
CI/CD ist ein recht neues Konzept in der Softwareentwicklung, in dem es grob gesagt darum geht, große Teile der Integration von neuem Code zu automatisieren und so weit es geht für die Entwickler zu vereinfachen. Es gibt keine klare Definition dieses Konzepts, oder seiner einzelnen Bestandteile „Continuous Integration“, „Continuous Delivery“ und „Continuous Deployment“, aber zahlreiche Artikel, die sich damit befassen. Ihr könnt also in die Tiefe gehen, wenn euch dieses Thema näher interessiert – zum Beispiel hier.
Mich persönlich interessiert in diesem Zusammenhang eigentlich nur die Tatsache, dass ich die Veröffentlichung meiner Apps ohne großen Aufwand (und Kosten) automatisieren kann, damit ich mich auf das konzentrieren kann, was mir Spaß macht: das Entwickeln neuer Features.
Dieser Teil von CI/CD lässt sich im „Continuous Deployment“ wiederfinden. Wie es im oben verlinkten Artikel so eloquent beschrieben wird:
In der Praxis bedeutet Continuous Deployment, dass App-Änderungen eines Entwicklers binnen weniger Minuten nach ihrer Erstellung live gehen können (vorausgesetzt, sie bestehen den automatischen Test). Dies erleichtert eine kontinuierliche Integration von User Feedback ungemein.
Wie kann ich meine App bei jedem Push neu generieren und veröffentlichen lassen?
CD funktioniert im Zusammenhang mit Git und Git-Providern sehr gut, indem eine Pipeline, also eine Reihe von Arbeitsschritten, durch einen Push zu einem bestimmten Branch ausgelöst wird. In unserem Fall sind diese Schritte einfach:
- Führe
npm run build
aus - Veröffentliche den
dist
-Ordner
Das kann natürlich noch deutlich komplexer werden, indem zum Beispiel vor der
Veröffentlichung automatisierte Tests ablaufen. Wie genau dieser Ablauf
konfiguriert wird, hängt von dem jeweiligen Git bzw. CI Provider ab. GitLab
bietet einen integrierten CI-Dienst an, den ihr aktivieren und konfigurieren
könnt, indem ihr eine .gitlab-ci.yml
in euer Repository pusht. Mehr erfahren
könnt ihr dazu in dieser
Dokumentation. Wir werden aber speziell für GitLab in einem nachfolgenden
Abschnitt noch ein konkretes Beispiel anführen.
Es bietet sich auch an, einen separaten Branch für die „veröffentlichte“ Version
eures Codes anzulegen, den ich meistens public
oder production
nenne. So
könnt ihr weiterhin die aktuellste Version eures Codes in eurem master
-Branch
ablegen und dort arbeiten, ohne diese Änderungen, die eventuell noch unfertig
sind direkt zu veröffentlichen.
Seid ihr dann bereit, eine neue Version eurer App zu veröffentlichen, merged
ihr euren master
-Branch in den production
-Branch und pusht diesen dann zu
eurem Git-Provider. Dieser Push löst die Pipeline aus und ein paar Minuten
später habt ihr die neue Version veröffentlicht.
Tipp:
Vergesst nicht, mit npm version patch/minor/major
die Version
eurer App zu erhöhen! Gerade bevor ihr sie veröffentlicht ist der richtige
Zeitpunkt dafür.
Wie ihr seht ist dieses System einfach und flexibel, sobald ihr es das erste Mal konfiguriert habt. Ihr könnt euch darauf konzentrieren, eure App zu schreiben und wenn ihr bereit seid, eine neue Version zu veröffentlichen, reichen, zwei, drei Terminal-Kommandos (oder ein einzelnes selbstgeschriebenes Skript 😉), um eure App vollautomatisch zu veröffentlichen.
Achtung:
CI-Dienstleister rechnen nach „Buildminuten“ ab, also der Zeit, die es dauert, euren Code zu „verpacken“ und zu veröffentlichen! Bei den meisten habt ihr einige Freiminuten im Monat, die ihr kostenlos nutzen könnt und für kleine Projekte wie eure App aus dem Kurs werdet ihr wohl nicht darüber hinaus kommen, aber ihr solltet dennoch im Hinterkopf behalten, dass diese Dienste natürlich nicht einfach nur kostenlos sind. Veröffentlicht eure Apps also nicht alle paar Sekunden neu, wenn ihr nicht bereit seid, den Preis dafür zu zahlen.
Was ist dieses Let’s Encrypt und warum ist das so wichtig?
Damit eure PWA auf Geräten funktionieren und über APIs auf Kameras und andere
Sensoren zugreifen kann, muss sie über einen sogenannten „Secure Context“
ausgeliefert werden. Das kann nur geschehen, indem ihr über ein SSL-Zertifikat
eure Identität bestätigt. In der Praxis bedeutet das, die URL eurer App muss
mit https
beginnen, nicht mit http
.
Das bedeutet, dass die Verbindung von eurer Website oder App zum Web-Server, der die Dateien ausliefert, verschlüsselt ist und nicht abgefangen und manipuliert werden kann, und dass die Website oder App auch wirklich die ist, für die sie sich ausgibt.
In der Vergangenheit war es sehr teuer, ein solches SSL-Zertifikat zu erhalten, das zudem noch, wie eure Domain auch, jährlich erneut bezahlt werden musste. Dank der Let’s Encrypt-Initiative hat sich aber alles verändert. Nun kann jeder vollautomatisch und kostenlos ein eigenes SLL-Zertifikat erhalten.
Viele Web-Hoster bieten es euch deshalb auch an, ein „kostenloses SSL Zertifikat über Let’s Encrypt“ zu erhalten, indem ihr einfach eine Checkbox anklickt. Solltet ihr aber jemals einen eigenen Server betreiben, könnt ihr auch dort einfach den Certbot von Let’s Encrypt installieren und somit automatisch das SSL-Zertifikat eures Servers ständig erneuern lassen. 👍
Let’s Encrypt ist also eine „freie, automatisierte und offene Zertifizierungsstelle“ für SSL-Zertifikate. Alle weiteren Informationen findet ihr auf der Website von Let’s Encrypt.
Wie kann ich PWAs kostenfrei hosten?
Da PWAs eigentlich nur statische Webseiten sind, d.h. nur aus ein paar kleinen Textdateien bestehen, ist es vergleichsweise günstig für einen Hosting-Provider sie zu hosten. Deshalb gibt es inzwischen sehr viele Anbieter, die es als Teil ihrer „kundenbindenden“ Leistungen anbieten, eure statischen Webseiten kostenlos für euch zu hosten, sofern sie innerhalb bestimmter Bandbreitenlimits und anderer Einschränkungen liegen, die ihr bitte den Allgemeinen Geschäftsbedingungen der Anbieter entnehmt.
Ich stelle euch hier zwei dieser Anbieter näher vor, mit denen ich in der Vergangenheit gute Erfahrungen gemacht habe, und die ich auch heute noch verwende.
GitLab Pages
Die erste und einfachste Option ist GitLab Pages. Damit könnt ihr eine statische
HTML-Website direkt von eurem Repository aus hosten und verwalten. Hier hoste
ich zum Beispiel diese Kurswebsite. Dank des CI-Diensts von GitLab werden alle
Änderungen, die ich in dem Repository der Seite vornehme nach einem Push
automatisch veröffentlicht und wie ihr seht bekomme ich sogar eine kostenlose
https://gitlab.io
-Domain (auch wenn ich natürlich problemlos auch meine eigene
Domain benutzen könnte).
GitLab Pages wird für euer Repository aktiviert, indem ihr ihm eine gültige
.gitlab-ci.yml
hinzufügt, die einen pages
-Job enthält. Ihr müsst hier wie
immer nicht alles genau verstehen, was passiert, aber es ist eigentlich recht
einfach. Diese Datei aktiviert den CI-Dienst GitLabs und verschiebt nach dem
„verpacken“ eurer App den dist
-Ordner in einen public
-Ordner, den GitLab
dann dazu verwendet, um die Seite zu hosten.
In der Dokumentation von Vue CLI gibt es einen eigenen Bereich, der euch den Code für diese Datei aufzeigt (ich habe sie hier für unsere Zwecke vereinfacht und neu kommentiert):
# .gitlab-ci.yml für euer Projekt-Root-Verzeichnis
pages: # der Name des CI-Jobs (muss pages sein)
image: node:latest # benutze das neuste Node.js
stage: deploy
script:
- npm ci # wie npm install, nur für CI Umgebungen, siehe https://docs.npmjs.com/cli/ci.html
- npm run build # App verpacken
- mv public public-vue # GitLab Pages braucht einen public-Folder, aber der wird auch von Vue verwendet, deshalb wird er temporär verschoben
- mv dist public # benenne den dist Ordner in public um
artifacts:
paths:
- public # sorge dafür, dass der public-Ordner nicht gelöscht wird, wenn das Skript vorbei ist, damit GitLab pages ihn veröffentlichen kann
only:
- production # führe dieses Skript nur für den production-Branch aus
Habt ihr die Datei in euer Repository gesteckt und etwas auf den Branch
production
gepusht, sollte GitLab nun das Skript ausführen und eure Seite
veröffentlichen. Die Domain, die ihr dabei standardmäßig erhaltet ist
https://euer-gitlab-nutzername.gitlab.io/name-des-repositories
. Wenn ihr also
nicht vor habt eine eigene Domain für eure App zu verwenden, müsst ihr
dafür sorgen, dass ihr den publicPath
innerhalb eurer vue.config.js
-Datei
dementsprechend anpasst:
module.exports = {
// …andere Optionen…
publicPath: process.env.NODE_ENV === 'production' // wenn die App für die Veröffentlichung verpackt wird
? '/' + process.env.CI_PROJECT_NAME + '/' // setze den Wert auf /name-des-repositories/
: '/' // ansonsten (also während der Entwicklung) lasse ihn auf '/'
// …andere Optionen…
}
Bitte beachtet auch, dass es aktuell keine Möglichkeit gibt, jegliche Anfragen
an eine so gehostete Datei auf die index.html
-Datei umzuleiten, ihr könnt also
nicht den History-Modus des Vue Router benutzen, wenn ihr auf GitLab Pages
hosten möchtet!
Netlify
Netlify ist der Betreiber eines globalen Content-Delivery-Networks (kurz CDN),
der es euch ebenfalls erlaubt kostenlos statische Websites zu hosten und euch
sogar eine kostenlose https://meine-app.netlify.app
-Domain gibt. Um dort eure
App zu hosten, braucht ihr einen Account, den ihr euch kostenlos anlegen könnt.
Habt ihr das getan, könnt ihr dort ein neues Projekt anlegen und mit eurem
GitLab Repository verbinden, was automatisch einen CI-Dienst einrichtet (ihr
braucht also keine.gitlab-ci.yml
). Dann müsst ihr nur noch die richtigen
Befehle in das Formular eintragen:
- Branch to Deploy:
production
- Build Command:
npm run build
- Publish Directory:
dist
Habt ihr das getan, wird eure App ebenfalls automatisch veröffentlicht, wann immer ihr einen Push im genannten Branch unternehmt. Ihr könnt dann den Namen eurer App, die Domain, und andere Einstellungen innerhalb der Netlify-Oberfläche verändern.
Falls ihr eure App lieber von Hand veröffentlichen möchtet, könnt ihr bei
Netlify außerdem auch einfach einen Ordner mit euren Dateien (in unserem Fall
den dist
-Ordner) in die Oberfläche ziehen.
Wenn ihr den History-Mode des Vue Router benutzt, müsst ihr außerdem noch eine
_redirects
-Datei im public
-Ordner eurer Vue-App ablegen, die jede Anfrage
auf die index.html
umleitet:
# Netlify Settings for PWA
/* /index.html 200
Achtung: Wenn ihr eine _redirects
-Datei anlegt, müsst ihr diese von eurem
generierten Service Worker ignorieren lassen, ansonsten kann dieser nicht
korrekt generiert werden. Fügt dazu folgende Zeile in das
workboxOptions
-Objekt in eurer vue.config.js
-Datei ein:
// vue.config.js
module.exports = {
// …andere Optionen…
pwa: {
// …andere Optionen…
workboxOptions: {
// …andere Optionen…
exclude: ['_redirects'], // ignoriere _redirects, um Fehler im Service Worker zu verhindern
// …andere Optionen…
},
},
// …andere Optionen…
}
Warum die Weiterleitung?
Wenn ihr den History-Mode verwendet, sieht es für den Browser so aus als wäre
unter dem Pfad meine-app.de/todos
eine index.html
-Datei, die aber in dieser
Form nicht existiert (es gibt ja nur die index.html
unter meine-app.de/
).
Damit eure App also richtig starten und dann über den Vue-Router auf die
angegebene URL wechseln kann, müsst ihr alle Anfragen zunächst auf die index.html
umleiten.
Welchen Provider soll ich benutzen?
Wie gesagt, es gibt Unmengen an Providern, bei denen ihr eure Apps hosten könnt, denn es muss ja einfach nur ein Web-Hoster sein. Ihr könnt also gerne auch den selben Hosting-Provider benutzen, auf dem ihr aktuell eure Portfolio-Webseiten hostet.
Die Beiden, die ich euch hier vorgestellt habe sind für eure Nutzen kostenlos, und besonders GitLab Pages hat den Vorteil, dass ihr alles, was eure App angeht an einem zentralen Ort habt: eurem Git-Repository. Allerdings gibt es die unangenehme Einschränkung mit den fehlenden Redirects, falls ihr den History-Mode verwenden möchtet.
Da ich in meinen Projekten immer den History-Mode benutze, verwende ich für meine Apps also lieber Netlify, aber statische Websites, die zum Beispiel über Gridsome generiert werden, wie die Kurswebsite, hoste ich auf GitLab Pages.
Nehmt einfach den Provider, der für euch am bequemsten ist – und behaltet im Hinterkopf, dass ihr euch auch immer zu einem späteren Zeitpunkt noch umentscheiden könnt.
Praxis / Nächste Sessions
Ihr seid jetzt mitten in der Entwicklung eurer Apps, deshalb wird der heutige Praxisteil der sein, mit dem wir auch die noch verbleibenden Sessions komplett füllen werden: ihr entwickelt eure Apps weiter.
Wie gehabt stehe ich euch bis 17:15 für Fragen und Hilfestellungen im Video-Call zur Verfügung, zu anderen Zeiten einfach per Mail oder Telegram.
Ansonsten bleibt mir nichts anderes übrig als zu sagen: frohes Schaffen! Ich hoffe ihr konntet alle etwas neues Lernen und wisst jetzt, wie man an die Entwicklung einer PWA herangehen kann. Alles, was wir besprochen haben wird euch auch weiterhin in diesen Skripten zur Verfügung stehen, aber bedenkt immer, dass die JavaScript-Welt nicht still steht und sich jeden Tag weiterentwickelt. Es kann also gut sein, dass in einem halben Jahr die Landschaft ganz anders aussieht und die Welt mit anderen Libraries, Frameworks und Technologien arbeitet.
Trotzdem bleiben die Prinzipien die selben: lest die Dokumentationen und haltet die Dinge so einfach wie möglich, dann sind euch keine Grenzen gesetzt. 😊
Ich bin sehr gespannt darauf, eure fertigen Apps zu sehen und zu testen! 🎉