"Offline eerst" is een applicatie-ontwikkelingsparadigma waarin ontwikkelaars ervoor zorgen dat de functionaliteit van een app niet wordt beïnvloed door een tijdelijk verlies van netwerkconnectiviteit. Progressieve webapplicaties, die aanvoelen als native applicaties maar werken als webapplicaties, zijn vaak gebouwd op dit paradigma.
Deze tutorial leert je hoe je een offline-first applicatie bouwt met Node.js en een SQLite-database. Laten we beginnen met een inleiding tot progressieve web-apps.
Inleiding tot PWA
Progressive Web Apps (PWA's) zijn web-apps die gebruikmaken van servicemedewerkers, manifesten en andere webplatformfuncties en progressieve verbeteringen om gebruikers een ervaring te bieden die vergelijkbaar is met native apps.
PWA's presteren soms beter dan native apps op het gebied van efficiëntie. Ze werken op aanvraag en zijn altijd beschikbaar zonder kostbaar smartphonegeheugen of gegevens te verbruiken. Gebruikers verbruiken minder data bij het kiezen van een PWA in plaats van een native versie van dezelfde applicatie. Ze kunnen de PWA nog steeds op hun startscherm opslaan; het is te installeren zonder dat een volledige download nodig is.
Wat zijn we aan het bouwen?
Om de kracht van progressieve webapplicaties te demonstreren, bouwen we een eenvoudige blogapplicatie.
De gebruiker kan ermee communiceren zoals andere PWA's, zoals de Twitter PWA. Laten we beginnen.
NodeJs-toepassing initialiseren
Laten we onze handen vuil maken. Om te beginnen, zullen we onze projectmap maken met de onderstaande opdracht:
mkdir PWA && cd PWA
Vervolgens initialiseren we een Node.js-toepassing met de onderstaande opdrachten:
npm init -y
De bovenstaande opdracht maakt een package.json
bestand voor de toepassing.
Maak vervolgens de volgende mappenstructuur in onze projectmap:
Een Express-server instellen
Laten we met onze applicatie-instellingen Express installeren om onze Node.js-server te maken met de onderstaande opdracht:
npm install express
Vervolgens maken we een aantal mappen en bestanden in de openbare map:
- css/style.css-bestand
- js/app.js-bestand
Maak vervolgens een index.js
bestand in de hoofdmap van het project met de volgende codefragmenten hieronder:
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"));
In het codefragment importeren we express om onze server en het pad te maken module. We hebben onze app geconfigureerd om onze statische bestanden weer te geven met behulp van de express.static methode, die het pad naar de statische map (public) neemt, hebben we de rootroute van onze applicatie gemaakt en de index.html weergegeven het dossier. Vervolgens hebben we de app geconfigureerd om te luisteren naar poort 8000 .
Verbinding maken met SQLite-database
Laten we met de serverconfiguratie voor onze applicatie onze applicatie maken en verbinden om onze blogdetails op te slaan. Voer om te beginnen de onderstaande opdracht uit om de sqlite3-afhankelijkheid te installeren.
npm install sqlite3
Dan, in het ingangspunt index.js
bestand, voeg dan het onderstaande codefragment toe om de applicatie te maken en te verbinden met een SQLite-database.
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.");
}
});
Vervolgens maken we een lijst met blogs die we in onze database opslaan en later aan de clientzijde weergeven met het onderstaande codefragment:
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.",
},
];
Elke blokpost in onze applicatie heeft een id , titel , avatar , en intro velden.
Maak nu een databasetabelnaam blogs en sla de blogdetails op die we zojuist hierboven hebben gemaakt met het onderstaande codefragment:
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}`,
]);
});
}
}
);
In het codefragment hebben we een tabel gemaakt blogs met behulp van de db.run. De db.run methode neemt een SQL-query als parameter, dan doorlopen we onze reeks blogs en voegen ze in de blogs-tabel die we zojuist hebben gemaakt met behulp van de js-kaartfunctie.
Databaserecords bekijken
Laten we nu de records bekijken die we zojuist hebben gemaakt met Arctype. Volg de onderstaande stappen om de records in uw SQLite-database te bekijken met Arctype:
- Installeer Arctype
- Voer de applicatie uit met
node index.js
een database maken - Start Arctype en klik op het tabblad SQLite
- Klik op het Selecteer SQLite-bestand en zoek de db.sqlite bestand gegenereerd toen u de server draaide.
- U zou de blogs-tabel moeten zien en de records die we maken, zoals weergegeven in de onderstaande schermafbeelding:
Geef de pagina weer
Op dit moment hebben we de applicatie verbonden met een SQLite-database en ook enkele records in de database ingevoegd. Open nu de index.html bestand en voeg de volgende codefragmenten hieronder toe:
<!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>
We hebben een eenvoudige opmaak gemaakt met links naar ons manifest in het bovenstaande bestand, dat we zullen maken in de volgende sectie, stijlen , en app.js bestanden.
Vervolgens maken we een blogs route in onze index.js bestand om de blogs terug te sturen naar de client-side.
...
app.get("/blogs", (req, res) => {
res.status(200).json({
blogs,
});
});
...
In onze public/js/app.js bestand, sturen we een ophaalverzoek naar het blog-eindpunt om de blogs van onze backend op te halen. Vervolgens doorlopen we de blogs, richten we ons op de container klas en laat ze zien.
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);
});
We zullen ook wat styling aan onze applicatie toevoegen in de public/css/style.css met onderstaand codefragment:
* {
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;
}
Open nu het package.json bestand en voeg het startscript toe.
"start": "node index.js"
Op dit punt hebben we onze applicatie ingesteld. Maar we kunnen onze applicatie niet draaien als de server niet draait of als er geen netwerkverbinding is voor productie. Laten we dat in het volgende gedeelte instellen.
Toepassing optimaliseren
We moeten onze applicatie compatibel maken met alle schermformaten. We zullen ook een themakleur toevoegen door de onderstaande markeringen toe te voegen in het hoofdgedeelte van onze index.html het dossier.
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#16a0d6e7"/>
Maak een manifest
We moeten onze app beschrijven en hoe deze zich zou moeten gedragen wanneer deze op het apparaat van de gebruiker is geïnstalleerd. Dit kunnen we doen door een manifest te maken.
Maak een manifest bestand in de hoofdmap van het project en voeg de volgende configuraties toe:
{
"name": "Blogger"
"short_name": "Blogger"
"start_url": "/",
"display": "standalone",
"background_color": "#0e9c95",
"theme_color": "#16a0d6e7",
"orientation": "portrait",
"icons": []
}
In ons manifest hebben we de volgende configuraties gedefinieerd:
- naam :dit definieert de weergavenaam van de app.
- short_name :dit definieert de naam die na installatie onder het app-pictogram wordt weergegeven.
- start_url :Dit vertelt de browser de root-URL van de applicatie.
- weergave :Dit vertelt de browser hoe de app moet worden weergegeven.
- background_color: Dit definieert de achtergrondkleur van de applicatie wanneer deze is geïnstalleerd.
- theme_color: Dit definieert de kleur van de statusbalk.
- oriëntatie: Dit definieert de richting die moet worden gebruikt tijdens de app-weergave.
- pictogrammen: Dit definieert de pictogrammen of afbeeldingen van verschillende groottes die moeten worden gebruikt als de startpictogrammen van onze app.
Het handmatig maken van onze startschermpictogrammen kan een zeer gecompliceerde taak zijn, maar maak je geen zorgen. We maken gebruik van een externe module die bekend staat als pwa-asset-generator om pictogrammen van verschillende groottes te genereren vanuit ons hoofdapp-pictogram in de openbare map met de onderstaande opdracht:
#change directory to the public folder
cd public
#generate icons
npx pwa-asset-generator logo.png icons
Met de bovenstaande opdracht worden pictogrammen . gemaakt map in de openbare map met veel pictogrammen voor onze applicatie, samen met enkele JSON op de terminal die we in onze pictogrammenreeks in het manifest zullen plakken.
De pictogrammenreeks in ons manifest zou er als volgt uit moeten zien:
"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"
}
]
De opdracht genereerde ook de opmaakkoppelingen naar de gegenereerde pictogrammen.
Kopieer en plak de opmaak in het head-gedeelte van de opmaak in de public/index.html bestand.
Servicemedewerkers instellen
Nu ons manifest is gemaakt, gaan we de servicemedewerkers instellen. Een servicemedewerker is een stukje JavaScript-code dat door uw browser op de achtergrond wordt uitgevoerd in een aparte thread om de cache te verwerken voor activa en gegevens die u opslaat voor toekomstige verzoeken om offline ondersteuning voor uw toepassing in te schakelen.
Maak dus een blogger.serviceWorker.js bestand in de openbare map. Voor de servicemedewerker zijn er veel gebeurtenissen (push, activeer, installeer, fetch, bericht, synchronisatie), maar voor de demonstratie in deze tutorial behandelen we de installeren, activeren, en ophalen evenementen. Daarvoor moeten we een array maken om alle activa op te slaan die we in onze applicatie hebben gebruikt.
const assets = [
"/",
"css/style.css",
"js/app.js",
"/images/blog1.jpg",
"/images/blog2.jpg",
"/images/blog3.jpg,"
];
Daarna luisteren we naar de installatie event om onze statische bestanden te registreren en op te slaan in de cache van de browser. Dit proces duurt enige tijd om te voltooien. Om het wachten over te slaan, gebruiken we 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);
})
);
});
...
Vervolgens moeten we de cache wissen om de oude activa te verwijderen wanneer de servicemedewerker wordt bijgewerkt. Daarvoor luisteren we naar de activeren codefragment hieronder:
...
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())
);
});
In het bovenstaande codefragment gebruiken we de waitUntil methode op de servicemedewerker. Deze methode wacht tot de actie is voltooid, en dan controleren we of de middelen die we proberen te wissen de middelen zijn van onze huidige app voordat we ze verwijderen.
Vervolgens hebben we de bestanden die in onze cache zijn opgeslagen nodig om ze te gebruiken.
self.addEventListener("fetch", function (evt) {
evt.respondWith(
fetch(evt.request).catch(() => {
return caches.open(BLOGGER_ASSETS).then((cache) => {
return cache.match(evt.request);
});
})
);
})
Wanneer een verzoek op de pagina wordt gedaan, zal PWA onze cache controleren en eruit lezen als er gegevens in de cache zijn in plaats van naar het netwerk te gaan. Gebruik vervolgens de respondWith methode, negeren we de standaardinstelling van de browser en maken we van ons evenement een belofte. Wanneer de cache compleet is, kunnen we de cache teruggeven die overeenkomt met de evt.request. Als de cache klaar is, kunnen we de cache teruggeven die overeenkomt met de evt.request.
We hebben onze servicemedewerker succesvol ingesteld. Laten we het nu beschikbaar maken in onze applicatie.
Registreer de servicemedewerker
Laten we nu onze servicemedewerker registreren in onze public/js/app.js bestand met het onderstaande codefragment:
...
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 controleren we of de browser van onze applicatie servicemedewerkers ondersteunt (natuurlijk ondersteunen niet alle browsers servicemedewerkers), en registreren vervolgens ons servicemedewerkerbestand.
Voer nu de applicatie uit met de onderstaande opdracht:
npm start
Ga naar localhost:8000 in je browser om de app te openen.
Google Lighthouse Check
Laten we nu eens kijken of we onze PWA correct hebben ingesteld met behulp van een Google Lighthouse-controle. Klik met de rechtermuisknop op uw browser en selecteer "inspecteren". Selecteer op de inspecteer-tabbladen vuurtoren en klik op rapport genereren. Als alles goed ging met je applicatie, zou je een uitvoer moeten zien zoals die in de onderstaande schermafbeelding:
We hebben met succes onze eerste applicatie gemaakt. U kunt de server ook stoppen om de applicatie in de offline modus te testen.
Conclusie
Progressive Web Apps (PWA) gebruiken moderne API's om verbeterde mogelijkheden, betrouwbaarheid en installeerbaarheid te bieden met een enkele codebase. Ze stellen uw eindgebruiker in staat uw applicatie te gebruiken, ongeacht of ze een internetverbinding hebben of niet. Voel je vrij om de repository te forken en extra functies aan het project toe te voegen. Veel succes!