„Offline first“ ist ein Anwendungsentwicklungsparadigma, bei dem Entwickler sicherstellen, dass die Funktionalität einer App durch einen vorübergehenden Verlust der Netzwerkverbindung nicht beeinträchtigt wird. Progressive Webanwendungen, die sich wie native Anwendungen anfühlen, aber wie Webanwendungen ausgeführt werden, basieren häufig auf diesem Paradigma.
In diesem Tutorial erfahren Sie, wie Sie mit Node.js und einer SQLite-Datenbank eine Offline-First-Anwendung erstellen. Beginnen wir mit einer Einführung in progressive Web-Apps.
Einführung in PWA
Progressive Web Apps (PWAs) sind Web-Apps, die Service Worker, Manifeste und andere Webplattformfunktionen sowie progressive Verbesserungen verwenden, um Benutzern ein Erlebnis zu bieten, das mit nativen Apps vergleichbar ist.
PWAs können native Apps in Bezug auf Effizienz manchmal übertreffen. Sie arbeiten nach Bedarf und sind immer verfügbar, ohne wertvollen Smartphone-Speicher oder Daten zu verbrauchen. Benutzer verbrauchen weniger Daten, wenn sie eine PWA einer nativen Version derselben Anwendung vorziehen. Sie können die PWA immer noch auf ihrem Startbildschirm speichern; es kann installiert werden, ohne dass ein vollständiger Download erforderlich ist.
Was bauen wir?
Um die Leistungsfähigkeit progressiver Webanwendungen zu demonstrieren, erstellen wir eine einfache Bloganwendung.
Der Benutzer kann damit wie mit anderen PWAs interagieren, beispielsweise mit der Twitter-PWA. Kommen wir gleich zur Sache.
NodeJs-Anwendung initialisieren
Machen wir uns die Hände schmutzig. Um zu beginnen, erstellen wir unseren Projektordner mit dem folgenden Befehl:
mkdir PWA && cd PWA
Dann initialisieren wir eine Node.js-Anwendung mit den folgenden Befehlen:
npm init -y
Der obige Befehl erstellt eine package.json
Datei für die Anwendung.
Erstellen Sie als nächstes die folgende Ordnerstruktur in unserem Projektordner:
Richten Sie einen Express-Server ein
Lassen Sie uns mit unserem Anwendungs-Setup Express installieren, um unseren Node.js-Server mit dem folgenden Befehl zu erstellen:
npm install express
Dann erstellen wir ein paar Ordner und Dateien im öffentlichen Ordner:
- css/style.css-Datei
- js/app.js-Datei
Erstellen Sie als Nächstes eine index.js
Datei im Projektstammverzeichnis mit den folgenden Codeschnipseln unten:
const express = require("express");
const path = require("path");
const app = express();
app.use(express.static(path.join(__dirname, "public")));
app.get("/", function (req, res) {
res.sendFile(path.join(__dirname, "public/index.html"));
});
app.listen(8000, () => console.log("Server is running on Port 8000"));
Im Code-Snippet importieren wir express um unseren Server und den Pfad zu erstellen Modul. Wir haben unsere App so konfiguriert, dass sie unsere statischen Dateien mit express.static rendert -Methode, die den Pfad zum statischen Ordner (öffentlich) nimmt, haben wir die Stammroute unserer Anwendung erstellt und die index.html gerendert Datei. Dann haben wir die App so konfiguriert, dass sie auf Port 8000 lauscht .
Mit SQLite-Datenbank verbinden
Lassen Sie uns mit dem Server-Setup für unsere Anwendung unsere Anwendung erstellen und verbinden, um unsere Blog-Details zu speichern. Führen Sie zunächst den folgenden Befehl aus, um die sqlite3-Abhängigkeit zu installieren.
npm install sqlite3
Dann im Einstiegspunkt index.js
fügen Sie den folgenden Codeausschnitt hinzu, um die Anwendung zu erstellen und mit einer SQLite-Datenbank zu verbinden.
const db = new sqlite3.Database("db.sqlite", (err) => {
if (err) {
// Cannot open database
console.error(err.message);
throw err;
} else {
console.log("Connected to the SQLite database.");
}
});
Als Nächstes erstellen wir eine Liste von Blogs, die wir in unserer Datenbank speichern und später mit dem folgenden Code-Snippet auf der Client-Seite rendern:
let blogs = [
{
id: "1",
title: "How To Build A RESTAPI With Javascript",
avatar: "images/coffee2.jpg",
intro: "iste odio beatae voluptas dolor praesentium illo facere optio nobis magni, aspernatur quas.",
},
{
id: "2",
title: "How to Build an Offline-First Application with Node.js,"
avatar: "images/coffee2.jpg",
"iste odio beatae voluptas dolor praesentium illo facere optio nobis magni, aspernatur quas.",
},
{
id: "3",
title: "Building a Trello Clone with React DnD",
avatar: "images/coffee2.jpg",
intro: "iste odio beatae voluptas dolor praesentium illo facere optio nobis magni, aspernatur quas.",
},
];
Jeder Block-Post in unserer Anwendung hat eine ID , Titel , Avatar , und Einleitung Felder.
Erstellen Sie nun eine Datenbanktabelle mit dem Namen blogs und speichern Sie die Blog-Details, die wir gerade oben erstellt haben, mit dem folgenden Code-Snippet:
db.run(
`CREATE TABLE blog (id INTEGER PRIMARY KEY AUTOINCREMENT, title text,avatar text,intro text)`,
(err) => {
if (err) {
// console.log(err)
// Table already created
} else {
// Table just created, creating some rows
var insert = "INSERT INTO blogs (title, avatar, intro) VALUES (?,?,?)";
blogs.map((blog) => {
db.run(insert, [
`${blog.title}`,
`${blog.avatar}`,
`${blog.intro}`,
]);
});
}
}
);
Im Code-Snippet haben wir eine Tabelle Blogs erstellt mit db.run. Die db.run -Methode nimmt eine SQL-Abfrage als Parameter, dann durchlaufen wir unser Array von Blogs und fügen sie in die Blogs-Tabelle ein, die wir gerade mit der js-Map-Funktion erstellt haben.
Datenbankeinträge anzeigen
Sehen wir uns nun die Datensätze an, die wir gerade mit Arctype erstellt haben. Führen Sie die folgenden Schritte aus, um die Datensätze in Ihrer SQLite-Datenbank mit Arctype anzuzeigen:
- Arctype installieren
- Führen Sie die Anwendung mit
node index.js
aus um eine Datenbank zu erstellen - Starten Sie Arctype und klicken Sie auf die Registerkarte SQLite
- Klicken Sie auf SQLite-Datei auswählen Schaltfläche und suchen Sie db.sqlite Datei, die beim Ausführen des Servers generiert wurde.
- Sie sollten die Blogs-Tabelle und die von uns erstellten Datensätze sehen, wie im folgenden Screenshot gezeigt:
Rendern Sie die Seite
An dieser Stelle haben wir die Anwendung mit einer SQLite-Datenbank verbunden und auch einige Datensätze in die Datenbank eingefügt. Öffnen Sie nun die index.html file und fügen Sie die folgenden Code-Snippets unten hinzu:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="stylesheet" href="css/style.css" />
<title>Blogger</title>
<link rel="manifest" href="manifest" />
</head>
<body>
<section>
<nav>
<h1>Blogger</h1>
<ul>
<li>Home</li>
<li class="active">Blog</li>
</ul>
</nav>
<div class="container"></div>
</section>
<script src="js/app.js"></script>
</body>
</html>
Wir haben ein einfaches Markup mit Links zu unserem Manifest in der obigen Datei erstellt, das wir im nächsten Abschnitt Stile erstellen werden und app.js Dateien.
Dann erstellen wir einen Blog Route in unserer index.js -Datei, um die Blogs an die Clientseite zurückzugeben.
...
app.get("/blogs", (req, res) => {
res.status(200).json({
blogs,
});
});
...
In unserer public/js/app.js -Datei senden wir eine Get-Anforderung an den Blog-Endpunkt, um die Blogs von unserem Back-End abzurufen. Dann durchlaufen wir die Blogs und zielen auf den Container ab Klasse und zeigen sie an.
let result = "";
fetch("http://localhost:8000/blogs")
.then((res) => res.json())
.then(({ rows } = data) => {
rows.forEach(({ title, avatar, intro } = rows) => {
result += `
<div class="card">
<img class="card-avatar" src="/${avatar}"/>
<h1 class="card-title">${title}</h1>
<p class="intro">${intro}</p>
<a class="card-link" href="#">Read</a>
</div>
`;
});
document.querySelector(".container").innerHTML = result;
})
.catch((e) => {
console.log(e);
});
Wir werden unserer Anwendung in der public/css/style.css auch etwas Styling hinzufügen mit dem folgenden Code-Snippet:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #fdfdfd;
font-size: 1rem;
}
section {
max-width: 900px;
margin: auto;
padding: 0.5rem;
text-align: center;
}
nav {
display: flex;
justify-content: space-between;
align-items: center;
}
ul {
list-style: none;
display: flex;
}
li {
margin-right: 1rem;
}
h1 {
color: #0e9c95;
margin-bottom: 0.5rem;
}
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
grid-gap: 1rem;
justify-content: center;
align-items: center;
margin: auto;
padding: 1rem 0;
}
.card {
display: flex;
align-items: center;
flex-direction: column;
width: 15rem auto;
background: #fff;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
border-radius: 10px;
margin: auto;
overflow: hidden;
}
.card-avatar {
width: 100%;
height: 10rem;
object-fit: cover;
}
.card-title {
color: #222;
font-weight: 700;
text-transform: capitalize;
font-size: 1.1rem;
margin-top: 0.5rem;
}
.card-link {
text-decoration: none;
background: #16a0d6e7;
color: #fff;
padding: 0.3rem 1rem;
border-radius: 20px;
margin: 10px;
}
.intro {
color: #c2c5c5;
padding: 10px;
}
.active {
color: #16a0d6e7;
}
Öffnen Sie nun die package.json Datei und fügen Sie das Startskript hinzu.
"start": "node index.js"
An diesem Punkt haben wir unsere Anwendung eingerichtet. Aber wir können unsere Anwendung nicht ausführen, wenn der Server nicht läuft oder wenn keine Netzwerkverbindung für die Produktion besteht. Lassen Sie uns das im nächsten Abschnitt einrichten.
Optimierung der Anwendung
Wir müssen unsere Anwendung mit allen Bildschirmgrößen kompatibel machen. Wir fügen auch eine Designfarbe hinzu, indem wir das Markup unten in den Kopfbereich unserer index.html einfügen Datei.
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#16a0d6e7"/>
Erstellen Sie ein Manifest
Wir müssen unsere App beschreiben und wie sie sich verhalten soll, wenn sie auf dem Gerät des Benutzers installiert wird. Wir können dies tun, indem wir ein Manifest erstellen.
Erstellen Sie ein Manifest Datei im Projektstammverzeichnis und fügen Sie die folgenden Konfigurationen hinzu:
{
"name": "Blogger"
"short_name": "Blogger"
"start_url": "/",
"display": "standalone",
"background_color": "#0e9c95",
"theme_color": "#16a0d6e7",
"orientation": "portrait",
"icons": []
}
In unserem Manifest haben wir die folgenden Konfigurationen definiert:
- Name :Dies definiert den Anzeigenamen der App.
- Kurzname :Dies definiert den Namen, der bei der Installation unter dem App-Symbol angezeigt wird.
- start_url :Dies teilt dem Browser die Stamm-URL der Anwendung mit.
- anzeigen :Dies teilt dem Browser mit, wie er die App anzeigen soll.
- Hintergrundfarbe: Dies definiert die Hintergrundfarbe der installierten Anwendung.
- Themenfarbe: Dies definiert die Farbe der Statusleiste.
- Ausrichtung: Dies definiert die während der App-Anzeige zu verwendende Ausrichtung.
- Symbole: Dies definiert die Symbole oder Bilder unterschiedlicher Größe, die als unsere App-Home-Symbole verwendet werden sollen.
Das manuelle Erstellen unserer Startbildschirmsymbole kann eine sehr komplizierte Aufgabe sein, aber keine Sorge. Wir nutzen ein Drittanbieter-Modul namens pwa-asset-generator, um Symbole unterschiedlicher Größe aus unserem Haupt-App-Symbol im öffentlichen Verzeichnis mit dem folgenden Befehl zu generieren:
#change directory to the public folder
cd public
#generate icons
npx pwa-asset-generator logo.png icons
Der obige Befehl erstellt ein Symbol Ordner innerhalb des öffentlichen Ordners mit vielen Symbolen für unsere Anwendung, zusammen mit etwas JSON auf dem Terminal, die wir in unser Icons-Array im Manifest einfügen werden.
Das Icons-Array in unserem Manifest sollte so aussehen:
"icons": [
{
"src": "public/icons/manifest-icon-192.maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "public/icons/manifest-icon-192.maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "public/icons/manifest-icon-512.maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "public/icons/manifest-icon-512.maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
Außerdem generierte der Befehl die Markup-Links zu den generierten Symbolen.
Kopieren Sie das Markup und fügen Sie es in den Head-Abschnitt des Markups in public/index.html ein Datei.
Dienstmitarbeiter einrichten
Nachdem unser Manifest erstellt wurde, richten wir die Service Worker ein. Ein Service Worker ist ein JavaScript-Code, den Ihr Browser in einem separaten Thread im Hintergrund ausführt, um den Cache für Assets und Daten zu verwalten, die Sie für zukünftige Anfragen speichern, um Offline-Support für Ihre Anwendung zu ermöglichen.
Erstellen Sie also eine blogger.serviceWorker.js Datei öffentlich Mappe. Für den Dienstmitarbeiter gibt es viele Ereignisse (Push, Aktivierung, Installation, Abruf, Nachricht, Synchronisierung), aber für die Demonstration in diesem Tutorial behandeln wir Installieren, Aktivieren und holen Veranstaltungen. Zuvor müssen wir ein Array erstellen, um alle Assets zu speichern, die wir in unserer Anwendung verwendet haben.
const assets = [
"/",
"css/style.css",
"js/app.js",
"/images/blog1.jpg",
"/images/blog2.jpg",
"/images/blog3.jpg,"
];
Dann hören wir uns die Installation an Ereignis zum Registrieren und Speichern unserer statischen Dateien im Cache des Browsers. Dieser Vorgang dauert einige Zeit. Um das Warten zu überspringen, verwenden wir skipWaiting().
const BLOGGER_ASSETS = "blogger-assets";
self.addEventListener("install", (installEvt) => {
installEvt.waitUntil(
caches
.open(BLOGGER_ASSETS)
.then((cache) => {
cache.addAll(assets);
})
.then(self.skipWaiting())
.catch((e) => {
console.log(e);
})
);
});
...
Dann müssen wir den Cache leeren, um die alten Assets zu entfernen, wenn der Service Worker aktualisiert wird. Dazu hören wir uns das activate an Codeschnipsel unten:
...
self.addEventListener("activate", function (evt) {
evt.waitUntil(
caches
.keys()
.then((keysList) => {
return Promise.all(
keysList.map((key) => {
if (key === BLOGGER_ASSETS) {
console.log(`Removed old cache from ${key}`);
return caches.delete(key);
}
})
);
})
.then(() => self.clients.claim())
);
});
Im obigen Code-Snippet verwenden wir waitUntil Methode auf dem Servicemitarbeiter. Diese Methode wartet auf das Ende der Aktion und überprüft dann, ob die Assets, die wir zu löschen versuchen, die Assets unserer aktuellen App sind, bevor wir sie löschen.
Als Nächstes benötigen wir die in unserem Cache gespeicherten Dateien, um sie verwenden zu können.
self.addEventListener("fetch", function (evt) {
evt.respondWith(
fetch(evt.request).catch(() => {
return caches.open(BLOGGER_ASSETS).then((cache) => {
return cache.match(evt.request);
});
})
);
})
Wenn auf der Seite eine Anfrage gestellt wird, überprüft PWA unseren Cache und liest daraus, ob sich Daten im Cache befinden, anstatt zum Netzwerk zu gehen. Verwenden Sie dann respondWith -Methode überschreiben wir die Standardeinstellung des Browsers und machen unsere Ereignisrückgabe zu einem Versprechen. Wenn der Cache vollständig ist, können wir den Cache zurückgeben, der der evt.request entspricht. Wenn der Cache bereit ist, können wir den Cache zurückgeben, der mit evt.request übereinstimmt.
Wir haben unseren Servicemitarbeiter erfolgreich eingestellt. Machen wir es jetzt in unserer Anwendung verfügbar.
Registrieren Sie den Servicemitarbeiter
Lassen Sie uns nun unseren Service Worker in unserer public/js/app.js registrieren Datei mit dem folgenden Code-Snippet:
...
if ("serviceWorker" in navigator) {
window.addEventListener("load", function () {
navigator.serviceWorker
.register("/blogger.serviceWorker.js")
.then((res) => console.log("service worker registered"))
.catch((err) => console.log("service worker not registered", err));
});
}
Hier prüfen wir, ob der Browser unserer Anwendung Servicemitarbeiter unterstützt (natürlich unterstützen nicht alle Browser Servicemitarbeiter) und registrieren dann unsere Servicemitarbeiterdatei.
Führen Sie nun die Anwendung mit dem folgenden Befehl aus:
npm start
Gehen Sie in Ihrem Browser zu localhost:8000, um auf die App zuzugreifen.
Google Lighthouse-Check
Lassen Sie uns nun überprüfen, ob wir unsere PWA mit einem Google Lighthouse-Check richtig eingerichtet haben. Klicken Sie mit der rechten Maustaste auf Ihren Browser und wählen Sie „Inspizieren“. Wählen Sie auf den Registerkarten „Inspizieren“ den Leuchtturm aus und klicken Sie auf „Bericht erstellen“. Wenn mit Ihrer Anwendung alles gut gelaufen ist, sollten Sie eine Ausgabe wie im folgenden Screenshot sehen:
Wir haben unsere erste Anwendung erfolgreich erstellt. Sie können den Server auch stoppen, um die Anwendung im Offline-Modus zu testen.
Fazit
Progressive Web Apps (PWA) verwenden moderne APIs, um erweiterte Funktionen, Zuverlässigkeit und Installierbarkeit mit einer einzigen Codebasis bereitzustellen. Sie ermöglichen Ihrem Endbenutzer, Ihre Anwendung unabhängig davon zu verwenden, ob er über eine Internetverbindung verfügt oder nicht. Sie sollten sich frei fühlen, das Repository zu forken und dem Projekt zusätzliche Funktionen hinzuzufügen. Viel Glück!