"Offline first" är ett applikationsutvecklingsparadigm där utvecklare säkerställer att en apps funktionalitet inte påverkas av en tillfällig förlust av nätverksanslutning. Progressiva webbapplikationer, som känns som inbyggda applikationer men körs som webbapplikationer, bygger ofta på detta paradigm.
Denna handledning kommer att lära dig hur du bygger en offline-först-applikation med Node.js och en SQLite-databas. Låt oss börja med en introduktion till progressiva webbappar.
Introduktion till PWA
Progressiva webbappar (PWA) är webbappar som använder tjänstearbetare, manifest och andra webbplattformsfunktioner och progressiv förbättring för att ge användarna en upplevelse som är jämförbar med inhemska appar.
PWA:er kan ibland överträffa inhemska appar när det gäller effektivitet. De fungerar på begäran och är alltid tillgängliga utan att förbruka värdefullt smartphoneminne eller data. Användare förbrukar mindre data när de väljer en PWA framför en inbyggd version av samma applikation. De kan fortfarande spara PWA på sin hemskärm; den kan installeras utan att en fullständig nedladdning behövs.
Vad bygger vi?
För att visa kraften i progressiva webbapplikationer kommer vi att bygga en enkel bloggapplikation.
Användaren kommer att kunna interagera med den som andra PWA:er, till exempel Twitter PWA. Låt oss komma direkt till det.
Initiera NodeJs applikation
Låt oss smutsa ner händerna. För att komma igång skapar vi vår projektmapp med kommandot nedan:
mkdir PWA && cd PWA
Sedan initierar vi en Node.js-applikation med kommandona nedan:
npm init -y
Kommandot ovan skapar en package.json
fil för ansökan.
Skapa sedan följande mappstruktur i vår projektmapp:
Konfigurera en Express-server
Med vår applikationsinställning, låt oss installera Express för att skapa vår Node.js-server med kommandot nedan:
npm install express
Sedan skapar vi ett par mappar och filer i den offentliga mappen:
- css/style.css-fil
- js/app.js-fil
Skapa sedan en index.js
fil i projektets rotkatalog med följande kodavsnitt nedan:
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"));
I kodavsnittet importerar vi express för att skapa vår server och sökvägen modul. Vi konfigurerade vår app för att återge våra statiska filer med express.static metod, som tar sökvägen till den statiska mappen (public), skapade vi rotrutten för vår applikation och återgav index.html fil. Sedan konfigurerade vi appen för att lyssna på port 8000 .
Anslut till SQLite Database
Med serverinställningen för vår applikation, låt oss skapa och ansluta vår applikation för att spara vår blogginformation. För att komma igång, kör kommandot nedan för att installera sqlite3-beroendet.
npm install sqlite3
Sedan, i ingångspunkten index.js
lägg till kodavsnittet nedan för att skapa och ansluta applikationen till en SQLite-databas.
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.");
}
});
Därefter skapar vi en lista med bloggar som vi lagrar i vår databas och renderar senare till klientsidan med kodavsnittet nedan:
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.",
},
];
Varje blockpost i vår applikation kommer att ha ett id , titel , avatar och intro fält.
Skapa nu ett databastabellnamn bloggar och spara blogginformationen vi just skapade ovan med kodavsnittet nedan:
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}`,
]);
});
}
}
);
I kodavsnittet skapade vi en tabell bloggar med hjälp av db.run. db.run metoden tar en SQL-fråga som en parameter, sedan går vi igenom vårt utbud av bloggar och infogar dem i bloggtabellen vi just skapade med hjälp av js map-funktionen.
Visa databasposter
Låt oss nu se de poster vi just skapade med Arctype. För att se posterna i din SQLite-databas med Arctype, följ stegen nedan:
- Installera Arctype
- Kör programmet med
node index.js
för att skapa en databas - Starta Arctype och klicka på fliken SQLite
- Klicka på Välj SQLite-fil och leta upp db.sqlite fil som skapades när du körde servern.
- Du bör se bloggtabellen och de poster vi skapar enligt skärmdumpen nedan:
Återge sidan
Vid det här laget har vi kopplat applikationen till en SQLite-databas och även infogat några poster i databasen. Öppna nu index.html fil och lägg till följande kodavsnitt nedan:
<!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>
Vi skapade en enkel uppmärkning med länkar till vårt manifest i filen ovan, som vi kommer att skapa i nästa avsnitt, stilar och app.js filer.
Sedan skapar vi en blogg rutt i vår index.js fil för att skicka tillbaka bloggarna till klientsidan.
...
app.get("/blogs", (req, res) => {
res.status(200).json({
blogs,
});
});
...
I vår public/js/app.js fil, skickar vi en get-förfrågan till bloggens slutpunkt för att hämta bloggarna från vår backend. Sedan går vi igenom bloggarna, riktar in oss på behållaren klass och visa dem.
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);
});
Vi kommer också att lägga till lite stil till vår applikation i public/css/style.css med kodavsnittet nedan:
* {
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;
}
Öppna nu package.json fil och lägg till startskriptet.
"start": "node index.js"
Vid det här laget har vi konfigurerat vår applikation. Men vi kan inte köra vår applikation när servern inte är igång eller när det inte finns någon nätverksanslutning för produktion. Låt oss ställa in det i nästa avsnitt.
Optimering av applikation
Vi måste göra vår applikation kompatibel med alla skärmstorlekar. Vi lägger också till en temafärg genom att lägga till markeringen nedan i rubriken i vår index.html fil.
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#16a0d6e7"/>
Skapa ett manifest
Vi behöver beskriva vår app och hur den ska bete sig när den är installerad på användarens enhet. Vi kan göra detta genom att skapa ett manifest.
Skapa ett manifest fil i projektets rotkatalog och lägg till följande konfigurationer:
{
"name": "Blogger"
"short_name": "Blogger"
"start_url": "/",
"display": "standalone",
"background_color": "#0e9c95",
"theme_color": "#16a0d6e7",
"orientation": "portrait",
"icons": []
}
I vårt manifest definierade vi följande konfigurationer:
- namn :Detta definierar appens visningsnamn.
- short_name :Detta definierar namnet som kommer att visas under appikonen när det installeras.
- start_url :Detta talar om för webbläsaren applikationens rot-URL.
- display :Detta talar om för webbläsaren hur appen ska visas.
- bakgrundsfärg: Detta definierar bakgrundsfärgen för programmet när det är installerat.
- tema_färg: Detta definierar färgen på statusfältet.
- orientering: Detta definierar orienteringen som ska användas under appvisningen.
- ikoner: Detta definierar ikoner eller bilder av olika storlekar som ska användas som ikoner för våra apphem.
Att skapa våra hemskärmsikoner manuellt kan vara en mycket komplicerad uppgift, men inte oroa dig. Vi kommer att dra nytta av en tredjepartsmodul känd som pwa-asset-generator för att generera ikoner i olika storlekar från vår huvudappikon i den offentliga katalogen med kommandot nedan:
#change directory to the public folder
cd public
#generate icons
npx pwa-asset-generator logo.png icons
Kommandot ovan skapar en ikon mapp i den offentliga mappen med många ikoner för vår applikation, tillsammans med några JSON på terminalen som vi kommer att klistra in i vår ikonmatris i manifestet.
Ikonarrayen i vårt manifest ska se ut så här:
"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"
}
]
Kommandot genererade också uppmärkningslänkarna till de genererade ikonerna.
Kopiera och klistra in märkningen i rubriken i märkningen i public/index.html fil.
Konfigurationstjänstmedarbetare
Med vårt manifest skapat, låt oss ställa in servicemedarbetarna. En serviceworker är en del av JavaScript-kod som din webbläsare kör i bakgrunden i en separat tråd för att hantera cachen för tillgångar och data som du sparar för framtida förfrågningar för att aktivera offlinestöd för din applikation.
Så skapa en blogger.serviceWorker.js fil i offentligheten mapp. För servicearbetaren finns det många händelser (push, aktivera, installera, hämta, meddelande, synkronisera), men för demonstrationen i den här handledningen kommer vi att täcka installera, aktivera, och hämta evenemang. Innan dess måste vi skapa en array för att lagra alla tillgångar vi använde i vår applikation.
const assets = [
"/",
"css/style.css",
"js/app.js",
"/images/blog1.jpg",
"/images/blog2.jpg",
"/images/blog3.jpg,"
];
Sedan lyssnar vi på installationen händelse för att registrera och spara våra statiska filer i webbläsarens cache. Denna process tar lite tid att slutföra. För att hoppa över väntan använder vi 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);
})
);
});
...
Sedan måste vi rensa cachen för att ta bort de gamla tillgångarna när servicearbetaren uppdateras. För det lyssnar vi på aktivera kodavsnitt nedan:
...
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())
);
});
I kodavsnittet ovan använder vi waitTill metod på servicearbetaren. Den här metoden väntar på att åtgärden ska avslutas och sedan kontrollerar vi om tillgångarna vi försöker rensa är tillgångarna i vår nuvarande app innan vi raderar dem.
Därefter behöver vi filerna lagrade i vår cache för att kunna använda dem.
self.addEventListener("fetch", function (evt) {
evt.respondWith(
fetch(evt.request).catch(() => {
return caches.open(BLOGGER_ASSETS).then((cache) => {
return cache.match(evt.request);
});
})
);
})
När en begäran görs på sidan kommer PWA att kontrollera vår cache och läsa från den om det finns data i cachen istället för att gå till nätverket. Använd sedan svara med metoden åsidosätter vi webbläsarens standard och gör att vårt evenemang ger ett löfte. När cachen är klar kan vi returnera cachen som motsvarar evt.request. När cachen är klar kan vi returnera cachen som matchar evt.request.
Vi har framgångsrikt konfigurerat vår servicearbetare. Låt oss nu göra det tillgängligt i vår applikation.
Registrera Service Worker
Låt oss nu registrera vår servicearbetare i vår public/js/app.js fil med kodavsnittet nedan:
...
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));
});
}
Här kontrollerar vi om vår applikations webbläsare stöder servicearbetare (naturligtvis stöder inte alla webbläsare servicearbetare), sedan registrerar vi vår servicearbetarfil.
Kör nu applikationen med kommandot nedan:
npm start
Gå till localhost:8000 i din webbläsare för att komma åt appen.
Google Lighthouse Check
Låt oss nu kontrollera om vi har ställt in vår PWA korrekt med en Google Lighthouse-kontroll. Högerklicka på din webbläsare och välj "inspektera". På inspekteringsflikarna väljer du fyr och klickar på generera rapport. Om allt gick bra med din applikation bör du se en utdata som den i skärmdumpen nedan:
Vi har framgångsrikt skapat vår första applikation. Du kan också stoppa servern för att testa applikationen i offlineläge.
Slutsats
Progressiva webbappar (PWA) använder moderna API:er för att tillhandahålla förbättrade möjligheter, pålitlighet och installationsbarhet med en enda kodbas. De tillåter din slutanvändare att använda din applikation oavsett om de har en internetanslutning eller inte. Du bör gärna dela förvaret och lägga till ytterligare funktioner till projektet. Lycka till!