Website Pribadi Offline dengan Workbox

Bila konten website kita yang sudah pernah dikunjungi sebelumnya oleh pengguna maka alangkah baiknya kita cache atau simpan data-data yang ada di dalam web browser. Mengapa? Karena untuk menghemat sekian kilobyte kuota internet yang dikeluarkan oleh pengguna.

Tidak semua pengguna tinggal di daerah kota dan akses internet di daerah pedesaan juga belum merata.

Dengan adanya service worker maka tidak perlu memuat sumber yang sama berulang-ulang dan tentunya menghemat sekian kilobyte kuota internet yang dikeluarkan oleh pengguna (kembali pada pernyataan awal). Kondisi-kondisi yang akan terjadi bila sebuah web menggunakan service worker sebagai berikut:

  1. Jika pengguna mengakses halaman website pribadi saya untuk pertama kali dan website pribadi saya disematkan service worker maka service worker akan diinstall dan meng-cache konten-konten seperti image, font, html, css, js yang diperlukan di dalam Cache Storage browser.
  2. Jika pengguna memuat ulang (refresh) halaman website pribadi saya maka yang akan dijalankan adalah konten yang sudah di cache oleh service worker di dalam Cache Storage browser sehingga halaman web tidak perlu meminta konten melalui internet. Sehingga jika koneksi internet mati maka halaman web yang dimuat masih berasal dari Cache Storage browser.
  3. Lanjutan dari nomor 2, jika koneksi internet hidup dan ada konten baru maka service worker akan membuat versi baru dan berinteraksi dengan internet serta konten baru tersebut akan diganti dengan konten lama yang ada di dalam Cache Storage. Namun, biasanya service worker yang baru ini akan berjalan dengan sempurna bila kita menutup tab browser mengakses halaman website pribadi saya, membuat tab baru dan mengakses kembali website pribadi saya.
  4. Jika koneksi internet mati dan pengguna membuka halaman lain website pribadi saya yang belum pernah diakses sebelumnya maka service worker akan menampilkan halaman bahwa kita dalam mode offline. Dengan catatan bahwa halaman tersebut sudah di cache saat pengguna mengakses halaman website pribadi saya untuk pertama kalinya.

Intinya adalah service worker mempercepat halaman web saat dimuat untuk kedua kalinya dan seterusnya (repeat view).

Saya pribadi masih belum begitu mahir menggunakan service worker dan saat mencari bagaimana cara membuat website pribadi offline, saya menemukan panduan dari website thepolyglotdeveloper.com.

Website thepolyglotdeveloper.com dibangun dengan Hugo, Workbox untuk library service worker memberikan pengalaman offline, dan Gulp sebagai task automator saat mem-build Hugo dan Workbox.

Pada bagian ini, saya hanya fokus untuk menjelaskan sintaks yang gunakan saat membuat service worker dengan workbox.

const gulp = require("gulp");
const workboxBuild = require("workbox-build");

gulp.task("generate-service-worker", () => {
    return workboxBuild.generateSW({
        cacheId: "thepolyglotdeveloper",
        globDirectory: "./public",
        globPatterns: [
            "**/*.{css,js,eot,ttf,woff,woff2,otf,jpg,webp}"
        ],
        swDest: "./public/sw.js",
        modifyUrlPrefix: {
            "": "/"
        },
        clientsClaim: true,
        skipWaiting: true,
        ignoreUrlParametersMatching: [/./],
        offlineGoogleAnalytics: true,
        maximumFileSizeToCacheInBytes: 50 * 1024 * 1024,
        runtimeCaching: [
            {
                urlPattern: /(?:\/)$/,
                handler: "staleWhileRevalidate",
                options: {
                    cacheName: "html",
                    expiration: {
                        maxAgeSeconds: 60 * 60 * 24 * 7,
                    },
                },
            },
            {
                urlPattern: /\.(?:png|jpg|jpeg|gif|bmp|webp|svg|ico)$/,
                handler: "cacheFirst",
                options: {
                    cacheName: "images",
                    expiration: {
                        maxEntries: 1000,
                        maxAgeSeconds: 60 * 60 * 24 * 365,
                    },
                },
            },
            {
                urlPattern: /\.(?:mp3|wav|m4a)$/,
                handler: "cacheFirst",
                options: {
                    cacheName: "audio",
                    expiration: {
                        maxEntries: 1000,
                        maxAgeSeconds: 60 * 60 * 24 * 365,
                    },
                },
            },
            {
                urlPattern: /\.(?:m4v|mpg|avi)$/,
                handler: "cacheFirst",
                options: {
                    cacheName: "videos",
                    expiration: {
                        maxEntries: 1000,
                        maxAgeSeconds: 60 * 60 * 24 * 365,
                    },
                },
            }
        ],
    });
});

Pertama, workbox-build merupakan modul yang digunakan untuk membuat dua mode yakni menghasilkan service worker (generateSW) dan meng-inject manifest (injectManifest). Di halaman situs dokumentasi Workbox oleh Google menjelaskan perbedaan di antara kedua mode ini.

Mode generateSW

Mode ini untuk membuat file service worker dan ditulis dalam repositori atau project yang kita buat.

๐Ÿ‘ Kapan Menggunakan generateSW

  • Kita ingin melakukan precache files.
  • Kita ingin membuat konfigurasi yang sederhana yang diperlukan. Contoh mendefinisikan routes dan strategies.

๐Ÿ‘Ž Kapan Tidak Menggunakan generateSW

  • Kita ingin menggunakan fitur lain dari service worker seperti Web Push.
  • Kita ingin mengimpor skrip tambahan atau logic tambahan.

Mode injectManifest

Mode ini untuk menghasilkan daftar URL yang ingin di precache, dan menambahkan precache manifest ke file service worker yang sudah dibuat.

๐Ÿ‘ Kapan Menggunakan injectManifest

  • Kita ingin kendali lebih pada service worker yang dibuat.
  • Kita ingin melakukan precache files.
  • Kita memerlukan kebutuhan kompleks pada urusan routing.
  • Kita ingin menggunakan fitur lain dari service worker seperti Web Push.

๐Ÿ‘Ž Kapan Tidak Menggunakan injectManifest

  • Kita ingin jalan termudah untuk menambahkan service worker ke dalam website kita.

Kedua, menggunakan properti globalDirectory untuk menentukan jalur direktori (directory path) untuk pre-caching. Jalur direktori tersebut semestinya adalah berkas (file) statis HTML dan sumber-sumber yang digunakan oleh Hugo. Properti globalDirectory yang diisi adalah direktori ./public yang mana merupakan hasil dari perintah dari hugo.

Ketiga, saat melakukan pre-caching, kita dapat menspesifikasikan berkas-berkas yang kita inginkan dalam properti globPatterns. Yang saya isi adalah css, js, eot, ttf, woff, woff2, otf, jpg dan webp.

Pada contoh di atas, kita ingin melakukan pre-cache semua CSS, JavaScript, font dan gambar di mana saja dalam direktori public. Kita tidak melakukan pre-cache HTML dan media lain selain gambar karena akan memakan ruang penyimpanan komputer atau ponsel pintar mereka dan membutuhkan proses yang banyak serta bandwidth yang besar. Cukup pre-cache yang benar-benar diperlukan.

Keempat, menggunakan properti swDest untuk menentukan hasil keluaran (output) file service worker. Intinya task dari Gulp akan mengeluarkan file JavaScript untuk direferensikan ke dalam file manifest. Keluaran ini berisi logic dari service worker yang kita tentukan dan berada di root direktori.

Kelima, berdasarkan bagaimana cara kerja Hugo, kita juga perlu untuk menspesifikasi sebuah awalan (prefix) di cache path kita di properti modifyUrlPrefix. Jika kita tidak melakukannya, maka kita akan men-cache file tetapi tidak pernah digunakan. Pada properti ini akan memberikan awalan garis miring ke setiap path file.

Keenam, sejak Hugo membangun semuanya dan kita tidak bergantung pada fungsionalitas Single Page Application (SPA), kita dapat memaksa service worker untuk segera aktif ketika diinstal menggunakan properti clientClaims dan skipWaiting. Kita juga dapat mengabaikan semua pencocokan URL query parameter jika beberapa URL kita memiliki informasi UTM atau serupa.

Terkadang sebuah website memiliki banyak sekali berkas, kita tidak ingin men-cache semua berkas yang ukurannya lebih dari 50 MB. Hal ini membawa kita untuk melalukan runtime caching yang mana berbeda dengan per-caching. Daripada men-cache semua file ketika sebuah web dimuat untuk pertama kalinya maka kita akan men-cache file yang sudah pernah dimuat sebelumnya atau sudah pernah dikunjungi oleh pengguna.

Kita dapat menspesifikasikan caching strategy untuk tipe file yang berbeda jika diperlukan seperti kode di bawah:

{
    urlPattern: /(?:\/)$/,
    handler: "staleWhileRevalidate",
    options: {
        cacheName: "html",
        expiration: {
            maxAgeSeconds: 60 * 60 * 24 * 7,
        },
    },
}

Ekspresi regular di atas tidak merujuk pada sebuah ekstensi file. Ini karena kita ingin cache HTML dan Hugo memberikan kita tautan yang cantik dibandingkan file dengan .html di URL. Kita menggunakan strategi staleWhileRevalidate yang mana sumber permintaan berasal dari cache dan network secara paralel. Ketika kita me-rebuild ulang website dengan platform Hugo, cache perlu kita diatur ulang, jika tidak maka daftar artikel tidak akan diperbaharui dan pengguna tidak dapat melihat konten terbaru kita.

Dengan strategi staleWhileRevalidate, memungkinkan pengguna menerima cache di permintaan (request) pertama, tetapi setidaknya di permintaan kedua akan mendapatkan konten baru. Jika tidak ada koneksi internet, cache akan terus digunakan, kecuali jika cache tersebut sudah kadarluarsa.

Berikutnya, perhatikan kode di bawah.

{
    urlPattern: /\.(?:png|jpg|jpeg|gif|bmp|webp|svg|ico)$/,
    handler: "cacheFirst",
    options: {
        cacheName: "images",
        expiration: {
            maxEntries: 1000,
            maxAgeSeconds: 60 * 60 * 24 * 365,
        },
    },
},

Strategi cacheFirst akan men-cache gambar-gambar dan kadaluarsa setelah satu tahun. Gambar juga kadaluarsa bila lebih dari 1000 gambar di dalam cache. Dengan strategi cacheFirst maka file yang digunakan adalah file yang bersumber dari cache. Jika gambar tidak muncul di cache maka akan diminta melalui network. Hal ini bagus untuk file yang tidak jarang diubah.

Setelah build task dijalankan maka hasilnya akan ada di dalam folder public/sw.js. Menggunakan file sw.js di direktori public tidaklah cukup. Kita perlu memberitahukan halaman-halaman website kita untuk menggunakan file ini. Biasanya, file service worker didaftarkan (register) di dalam tag <body> sebelum penutup tag <body> dengan sintaks di bawah.

<script>
    if("serviceWorker" in navigator) {
        window.addEventListener("load", () => {
            navigator.serviceWorker.register("/sw.js").then(swReg => {}).catch(err => {
                console.error('Service Worker Error', err);
            });
        });
    }
</script>

Perlu diingat bahwa file sw.js harus berada dalam root direktori public dan service worker harus diregistrasi ketika semua aset seperti CSS, gambar dan JS sudah dimuat yang sudah dibantu oleh event window.load.

Ini adalah pengalaman saya menggunakan Workbox untuk production site pertama kalinya apalagi saya mengimplementasikannya di web pribadi saya. Semoga bermanfaat.