Memasang Pwa Di Hugo
Apa itu PWA?
Istilah Progressive Web App diperkenalkan oleh Alex Russel, salah seorang Google Chrome engineer, dan Frances Berriman, desainer yang bekerja untuk Google pada tahun 2015. Google mendefinisikan PWA sebagai berikut:
Progressive Web Apps use modern web capabilities to deliver an app-like user experience. They evolve from pages in browser tabs to immersive, top-level apps, maintaining the web’s low friction at every moment.
Tujuan utama PWA adalah untuk memungkinkan web developer mengubah web yang sudah ada agar bisa berperilaku layaknya aplikasi mobile native tanpa melakukan banyak perubahan atau menambah programmer khusus. Tujuan ini bisa dicapai berkat kumpulan teknologi web. Teknologi ini memungkinkan aplikasi web tradisional untuk bisa diakses tanpa koneksi internet (offline), bisa dipasang di homescreen seperti aplikasi mobile native, bisa melakukan sinkronisasi data ke server, bisa mengirim push notification, dan-lain lain.
Menurut Google Developer, karakteristik dari Progressive Web App adalah
Progressive
Dapat berjalan pada setiap browser dan pada setiap browser.
Responsive
Dapat berjalan pada berbagai ukuran layar dari berbagai peralatan.
Connectivity independent
Aplikasi dapat berfungsi dengan baik meski jaringan sedang lambat bahkan aplikasi dapat berfungsi dengan baik meski jaringan sedang down.
App-like
Tampilan seperti tampilan native mobile.
Fresh
Selalu update dengan data atau informasi terkini
Safe
Dilayani oleh HTTPS yang mencegah pencurian informasi
Installable
Pengguna dapat memanage aplikasi PWA seperti aplikasi native mobile
Bagaimana cara menambahkan PWA ke Hugo?
Mengunakan fitur PWA paling tidak membutuhkan Web App Manifest dan Service Worker. Web app manifest adalah file JSON sederhana yang mengontrol bagaimana aplikasi ditampilkan dan dijalankan di sisi pengguna. Umumnya file ini diberi nama manifest.json. Saat aplikasi pertama kali dibuka di browser, browser akan membaca file manifest, mengunduh resource dan menampilkan konten. Service Worker adalah skrip yang dijalankan oleh browser di latar belakang, yang terpisah dengan skrip lain di halaman web browser. Service worker ditulis menggunakan bahasa pemrograman JavaScript, namun dipanggil dengan cara yang berbeda dari kode JavaScript pada umumnya. Dengan menggunakan service worker, kita dapat memanfaatkan resource yang telah disimpan di dalam cache untuk menampilkan bahkan dalam mode jaringan offline.
Buat Ikon Aplikasi
PWA tersedia offline dan dapat dipasang di homescreen layaknya aplikasi mobile native. Ini memerlukan ikon dengan ukuran berbeda untuk situs web Anda. Anda dapat menggunakan layanan seperti Favicomatic untuk membuat ikon aplikasi dengan proses satu klik.
Membuat Web App Manifest
Buat file baru bernama manifest.json di folder /static pada template Hugo yang Anda gunakan dan contoh kode di bawah ini sesuaikan dengan keinginan :
{
"name": "MisterThree",
"short_name": "MisterThree",
"icons": [{
"src": "/images/apple-icon-180x180.jpg",
"sizes": "180x180",
"type": "image/png",
"purpose": "any maskable"
}, {
"src": "/images/android-icon-192x192.jpg",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
}, {
"src": "/images/android-icon-512x512.jpg",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}],
"start_url": "/",
"display": "stAndalone",
"orientation" : "any",
"background_color": "#FFFFFF",
"theme_color": "#FFFFFF",
"description": "Website tempat belajar coding dengan tutorial terupdate dan mudah dipahami pemula."
}
Mendaftarkan Web App Manifest
Tambahkan tautan manifest.json di header template, biasanya di /themes/layouts/partials/head.html tergantung template yang Anda gunakan.
<link rel="manifest" href="/manifest.json">
Untuk menguji apakah manifest.json sudah terpasang dengan benar, bila menggunakan browser Google Chrome, kita dapat memanfaatkan Chrome DevTools untuk memantau aktivitas aplikasi webmu. Kamu dapat menampilkan DevTools pada browser Chrome dengan menekan tombol keyboard Ctrl+Shift+i atau mengklik kanan pada layar browser dan memilih opsi Inspect pada menu popup.
Pada tab Aplikasi, klik Manifest .
Membuat Service Worker
Buat file javascript baru sw.js di /themes/static. Ini contoh dari blog saya Anda dapat menyesuaikan dengan kebutuhan Anda.
const CACHE_VERSION = 1;
const BASE_CACHE_FILES = [
'/',
'/blog/',
'/tags/',
'/404.html',
'/fonts/google-sans/regular/latin.woff2',
'/fonts/material-icons/regular.woff2',
'/css/style.min.css',
'/js/script.min.js',
'/manifest.json',
'/favicon.ico',
'/img/lockup.svg',
'/img/logo.svg',
'/index.json'
];
const OFFLINE_CACHE_FILES = [
'/offline/',
'/css/style.min.css',
'/js/script.min.js'
];
const NOT_FOUND_CACHE_FILES = [
'/404.html',
'/css/style.min.css',
'/js/script.min.js'
];
const OFFLINE_PAGE = '/offline/index.html';
const NOT_FOUND_PAGE = '/404.html';
const CACHE_VERSIONS = {
assets: 'assets-v' + CACHE_VERSION,
content: 'content-v' + CACHE_VERSION,
offline: 'offline-v' + CACHE_VERSION,
notFound: '404-v' + CACHE_VERSION,
};
// Define MAX_TTL's in SECONDS for specific file extensions
const MAX_TTL = {
'/': 3600,
html: 3600,
json: 86400,
js: 86400,
css: 86400,
};
const CACHE_BLACKLIST = [
(str) => {
return !str.startsWith('http://localhost') ;
},
];
const SUPPORTED_METHODS = [
'GET',
];
/**
* isBlackListed
* @param {string} url
* @returns {boolean}
*/
function isBlacklisted(url) {
return (CACHE_BLACKLIST.length > 0) ? !CACHE_BLACKLIST.filter((rule) => {
if(typeof rule === 'function') {
return !rule(url);
} else {
return false;
}
}).length : false
}
/**
* getFileExtension
* @param {string} url
* @returns {string}
*/
function getFileExtension(url) {
let extension = url.split('.').reverse()[0].split('?')[0];
return (extension.endsWith('/')) ? '/' : extension;
}
/**
* getTTL
* @param {string} url
*/
function getTTL(url) {
if (typeof url === 'string') {
let extension = getFileExtension(url);
if (typeof MAX_TTL[extension] === 'number') {
return MAX_TTL[extension];
} else {
return null;
}
} else {
return null;
}
}
/**
* installServiceWorker
* @returns {Promise}
*/
function installServiceWorker() {
return Promise.all(
[
caches.open(CACHE_VERSIONS.assets)
.then(
(cache) => {
return cache.addAll(BASE_CACHE_FILES);
}
),
caches.open(CACHE_VERSIONS.offline)
.then(
(cache) => {
return cache.addAll(OFFLINE_CACHE_FILES);
}
),
caches.open(CACHE_VERSIONS.notFound)
.then(
(cache) => {
return cache.addAll(NOT_FOUND_CACHE_FILES);
}
)
]
);
}
/**
* cleanupLegacyCache
* @returns {Promise}
*/
function cleanupLegacyCache() {
let currentCaches = Object.keys(CACHE_VERSIONS)
.map(
(key) => {
return CACHE_VERSIONS[key];
}
);
return new Promise(
(resolve, reject) => {
caches.keys()
.then(
(keys) => {
return legacyKeys = keys.filter(
(key) => {
return !~currentCaches.indexOf(key);
}
);
}
)
.then(
(legacy) => {
if (legacy.length) {
Promise.all(
legacy.map(
(legacyKey) => {
return caches.delete(legacyKey)
}
)
)
.then(
() => {
resolve()
}
)
.catch(
(err) => {
reject(err);
}
);
} else {
resolve();
}
}
)
.catch(
() => {
reject();
}
);
}
);
}
self.addEventListener(
'install', event => {
event.waitUntil(installServiceWorker());
}
);
// The activate handler takes care of cleaning up old caches.
self.addEventListener(
'activate', event => {
event.waitUntil(
Promise.all(
[
cleanupLegacyCache(),
]
)
.catch(
(err) => {
event.skipWaiting();
}
)
);
}
);
self.addEventListener(
'fetch', event => {
event.respondWith(
caches.open(CACHE_VERSIONS.content)
.then(
(cache) => {
return cache.match(event.request)
.then(
(response) => {
if (response) {
let headers = response.headers.entries();
let date = null;
for (let pair of headers) {
if (pair[0] === 'date') {
date = new Date(pair[1]);
}
}
if (date) {
let age = parseInt((new Date().getTime() - date.getTime()) / 1000);
let ttl = getTTL(event.request.url);
if (ttl && age > ttl) {
return new Promise(
(resolve) => {
return fetch(event.request)
.then(
(updatedResponse) => {
if (updatedResponse) {
cache.put(event.request, updatedResponse.clone());
resolve(updatedResponse);
} else {
resolve(response)
}
}
)
.catch(
() => {
resolve(response);
}
);
}
)
.catch(
(err) => {
return response;
}
);
} else {
return response;
}
} else {
return response;
}
} else {
return null;
}
}
)
.then(
(response) => {
if (response) {
return response;
} else {
return fetch(event.request)
.then(
(response) => {
if(response.status < 400) {
if (~SUPPORTED_METHODS.indexOf(event.request.method) && !isBlacklisted(event.request.url)) {
cache.put(event.request, response.clone());
}
return response;
}
else {
return caches.open(CACHE_VERSIONS.notFound).then((cache) => {
return cache.match(NOT_FOUND_PAGE);
})
}
}
)
.then((response) => {
if(response) {
return response;
}
})
.catch(
() => {
return caches.open(CACHE_VERSIONS.offline)
.then(
(offlineCache) => {
return offlineCache.match(OFFLINE_PAGE)
}
)
}
)
}
}
)
.catch(
(error) => {
console.error(' Error in fetch handler:', error);
throw error;
}
);
}
)
);
}
);
Tambahkan Service Worker
Untuk meload service worker, biasanya ditaruh di footer dari template yang Anda gunakan. Berikut potongan kodenya :
<script>
if('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js', { scope: '/' })
.then(function(registration) {
//console.log('Service Worker Registered');
});
navigator.serviceWorker
.ready
.then(function(registration) {
//console.log('Service Worker Ready');
});
}
</script>
Lihat hasilnya
Kembali ke Chrome DevTools, pada tab Aplikasi klik Service Worker untuk melihat apakah Service Worker sudah bekerja di aplikasi webmu.
Buka tab Audit pilih Lighthouse klik Generate report dan lihat hasilnya.
Sekarang situs kita secara otomatis menjadi PWA.





