Lebih dari sekadar SPA - arsitektur alternatif untuk PWA Anda

Mari kita bahas... arsitektur?

Saya akan membahas topik penting, tetapi berpotensi disalahpahami: Arsitektur yang Anda gunakan untuk aplikasi web, dan khususnya, bagaimana keputusan arsitektur Anda berperan saat Anda membuat aplikasi web progresif.

"Arsitektur" mungkin terdengar tidak jelas, dan mungkin tidak langsung terlihat mengapa hal ini penting. Nah, salah satu cara untuk memikirkan arsitektur adalah dengan mengajukan pertanyaan berikut kepada diri Anda sendiri: Saat pengguna mengunjungi halaman di situs saya, HTML apa yang dimuat? Lalu, apa yang dimuat saat mereka mengunjungi halaman lain?

Jawaban atas pertanyaan tersebut tidak selalu mudah, dan setelah Anda mulai memikirkan aplikasi web progresif, semuanya bisa menjadi lebih rumit. Jadi, tujuan saya adalah memandu Anda memahami salah satu kemungkinan arsitektur yang saya anggap efektif. Di sepanjang artikel ini, saya akan melabeli keputusan yang saya buat sebagai "pendekatan saya" dalam membangun aplikasi web progresif.

Anda bebas menggunakan pendekatan saya saat membangun PWA Anda sendiri, tetapi pada saat yang sama, selalu ada alternatif lain yang valid. Saya berharap dengan melihat bagaimana semua bagiannya saling terhubung, Anda akan terinspirasi dan merasa mampu menyesuaikannya dengan kebutuhan Anda.

PWA Stack Overflow

Untuk melengkapi artikel ini, saya membuat PWA Stack Overflow. Saya menghabiskan banyak waktu untuk membaca dan berkontribusi di Stack Overflow, dan saya ingin membuat aplikasi web yang akan memudahkan penjelajahan pertanyaan umum untuk topik tertentu. Fitur ini dibangun di atas Stack Exchange API publik. Project ini bersifat open source, dan Anda dapat mempelajari lebih lanjut dengan membuka project GitHub.

Aplikasi Multi-halaman (MPA)

Sebelum membahas detailnya, mari kita definisikan beberapa istilah dan jelaskan bagian teknologi yang mendasarinya. Pertama, saya akan membahas apa yang saya sebut sebagai "Aplikasi Multi Halaman", atau "MPA".

MPA adalah nama keren untuk arsitektur tradisional yang digunakan sejak awal web. Setiap kali pengguna membuka URL baru, browser secara progresif merender HTML khusus untuk halaman tersebut. Tidak ada upaya untuk mempertahankan status halaman atau konten di antara navigasi. Setiap kali Anda membuka halaman baru, Anda memulai dari awal.

Hal ini berbeda dengan model aplikasi satu halaman (SPA) untuk membangun aplikasi web, yang mana browser menjalankan kode JavaScript untuk memperbarui halaman yang ada saat pengguna mengunjungi bagian baru. SPA dan MPA sama-sama merupakan model yang valid untuk digunakan, tetapi untuk postingan ini, saya ingin mempelajari konsep PWA dalam konteks aplikasi multi-halaman.

Cepat dan andal

Anda telah mendengar saya (dan banyak orang lainnya) menggunakan frasa "progressive web app", atau PWA. Anda mungkin sudah mengetahui beberapa materi latar belakang di tempat lain di situs ini.

Anda dapat menganggap PWA sebagai aplikasi web yang memberikan pengalaman pengguna terbaik, dan yang benar-benar mendapatkan tempat di layar utama pengguna. Akronim "FIRE", yang merupakan singkatan dari Fast (Cepat), Integrated (Terintegrasi), Reliable (Andal), dan Engaging (Menarik), merangkum semua atribut yang perlu dipertimbangkan saat membangun PWA.

Dalam artikel ini, saya akan berfokus pada sebagian kecil atribut tersebut: Cepat dan Dapat Diandalkan.

Cepat: Meskipun "cepat" memiliki arti yang berbeda dalam konteks yang berbeda, saya akan membahas manfaat kecepatan pemuatan sesedikit mungkin dari jaringan.

Andal: Namun, kecepatan mentah saja tidak cukup. Agar terasa seperti PWA, aplikasi web Anda harus andal. Aplikasi harus cukup tangguh untuk selalu memuat sesuatu, meskipun hanya halaman error yang disesuaikan, terlepas dari status jaringan.

Cepat dan andal: Terakhir, saya akan sedikit mengubah definisi PWA dan melihat apa artinya membangun sesuatu yang cepat dan andal. Tidak cukup hanya cepat dan andal saat Anda berada di jaringan latensi rendah. Menjadi cepat dan andal berarti kecepatan aplikasi web Anda konsisten, terlepas dari kondisi jaringan yang mendasarinya.

Teknologi Pendukung: Service Worker + Cache Storage API

PWA menetapkan standar tinggi untuk kecepatan dan ketahanan. Untungnya, platform web menawarkan beberapa elemen untuk mewujudkan performa semacam itu. Saya merujuk pada service worker dan Cache Storage API.

Anda dapat membuat pekerja layanan yang memantau permintaan masuk, meneruskan beberapa permintaan ke jaringan, dan menyimpan salinan respons untuk penggunaan di masa mendatang, melalui Cache Storage API.

Service worker menggunakan Cache Storage API untuk menyimpan salinan respons
          jaringan.

Pada saat aplikasi web membuat permintaan yang sama, pekerja layanannya dapat memeriksa cache-nya dan hanya menampilkan respons yang di-cache sebelumnya.

Service worker menggunakan Cache Storage API untuk merespons, melewati jaringan.

Menghindari jaringan jika memungkinkan adalah bagian penting untuk menawarkan performa cepat yang andal.

JavaScript "Isomorfik"

Satu lagi konsep yang ingin saya bahas adalah apa yang terkadang disebut sebagai JavaScript "isomorfik", atau "universal". Sederhananya, ini adalah gagasan bahwa kode JavaScript yang sama dapat dibagikan di antara lingkungan runtime yang berbeda. Saat membuat PWA, saya ingin membagikan kode JavaScript antara server backend dan pekerja layanan.

Ada banyak pendekatan yang valid untuk membagikan kode dengan cara ini, tetapi pendekatan saya adalah menggunakan modul ES sebagai kode sumber definitif. Kemudian, saya mentranspilasi dan menggabungkan modul tersebut untuk server dan pekerja layanan menggunakan kombinasi Babel dan Rollup. Dalam project saya, file dengan ekstensi file .mjs adalah kode yang ada dalam modul ES.

Server

Dengan mengingat konsep dan terminologi tersebut, mari kita pelajari cara saya membangun PWA Stack Overflow. Saya akan mulai dengan membahas server backend, dan menjelaskan cara kerjanya dalam arsitektur secara keseluruhan.

Saya mencari kombinasi backend dinamis dengan hosting statis, dan pendekatan saya adalah menggunakan platform Firebase.

Firebase Cloud Functions akan otomatis menyiapkan lingkungan berbasis Node saat ada permintaan masuk, dan terintegrasi dengan framework HTTP Express populer, yang sudah saya kenal. Selain itu, Cloud CDN menawarkan hosting siap pakai untuk semua resource statis situs saya. Mari kita lihat cara server menangani permintaan.

Saat browser membuat permintaan navigasi terhadap server kami, browser akan melalui alur berikut:

Ringkasan pembuatan respons navigasi di sisi server.

Server merutekan permintaan berdasarkan URL, dan menggunakan logika pembuatan template untuk membuat dokumen HTML lengkap. Saya menggunakan kombinasi data dari Stack Exchange API, serta fragmen HTML parsial yang disimpan server secara lokal. Setelah mengetahui cara merespons, pekerja layanan dapat mulai melakukan streaming HTML kembali ke aplikasi web kita.

Ada dua bagian dari gambaran ini yang perlu dipelajari lebih lanjut: perutean dan pembuatan template.

Pemilihan rute

Untuk perutean, pendekatan saya adalah menggunakan sintaksis perutean bawaan framework Express. Cukup fleksibel untuk mencocokkan awalan URL sederhana, serta URL yang menyertakan parameter sebagai bagian dari jalur. Di sini, saya membuat pemetaan antara nama rute dan pola Express yang mendasarinya untuk dicocokkan.

const routes = new Map([
  ['about', '/about'],
  ['questions', '/questions/:questionId'],
  ['index', '/'],
]);

export default routes;

Kemudian, saya dapat mereferensikan pemetaan ini langsung dari kode server. Jika ada kecocokan untuk pola Express tertentu, handler yang sesuai akan merespons dengan logika pembuatan template khusus untuk rute yang cocok.

import routes from './lib/routes.mjs';
app.get(routes.get('index'), as>ync (req, res) = {
  // Templating logic.
});

Templating sisi server

Lalu, seperti apa logika pembuatan template tersebut? Nah, saya menggunakan pendekatan yang menggabungkan fragmen HTML parsial secara berurutan, satu per satu. Model ini sangat cocok untuk streaming.

Server segera mengirim kembali beberapa boilerplate HTML awal, dan browser dapat segera merender halaman parsial tersebut. Saat server menyusun sumber data lainnya, server akan melakukan streaming ke browser hingga dokumen selesai.

Untuk melihat maksud saya, lihat kode Express untuk salah satu rute kami:

app.get(routes.get('index'), async (req>, res) = {
  res.write(headPartial + navbarPartial);
  const tag = req.query.tag || DEFAULT_TAG;
  const data = await requestData(...);
  res.write(templates.index(tag, data.items));
  res.write(footPartial);
  res.end();
});

Dengan menggunakan metode write() objek response, dan mereferensikan template parsial yang disimpan secara lokal, saya dapat memulai streaming respons dengan segera, tanpa memblokir sumber data eksternal. Browser mengambil HTML awal ini dan langsung merender antarmuka yang bermakna dan pesan pemuatan.

Bagian berikutnya di halaman kita menggunakan data dari Stack Exchange API. Untuk mendapatkan data tersebut, server kita perlu membuat permintaan jaringan. Aplikasi web tidak dapat merender apa pun hingga mendapatkan respons kembali dan memprosesnya, tetapi setidaknya pengguna tidak menatap layar kosong saat menunggu.

Setelah aplikasi web menerima respons dari Stack Exchange API, aplikasi tersebut akan memanggil fungsi pembuatan template kustom untuk menerjemahkan data dari API ke HTML yang sesuai.

Bahasa template

Templating bisa menjadi topik yang sangat kontroversial, dan apa yang saya gunakan hanyalah salah satu pendekatan dari sekian banyak pendekatan yang ada. Anda sebaiknya mengganti solusi Anda sendiri, terutama jika Anda memiliki hubungan lama dengan framework template yang ada.

Yang masuk akal untuk kasus penggunaan saya adalah hanya mengandalkan literal template JavaScript, dengan beberapa logika yang dipecah menjadi fungsi helper. Salah satu hal menarik tentang membangun MPA adalah Anda tidak perlu melacak update status dan merender ulang HTML, sehingga pendekatan dasar yang menghasilkan HTML statis berhasil bagi saya.

Jadi, berikut contoh cara saya membuat template bagian HTML dinamis dari indeks aplikasi web. Seperti halnya rute saya, logika pembuatan template disimpan dalam modul ES yang dapat diimpor ke server dan pekerja layanan.

export function index(tag, items) {
  const title = `<h3>Top "${escape(tag)}"< Qu>estions/h3`;
  cons<t form = `form me>tho<d=&qu>ot;GET".../form`;
  const questionCards = i>tems
    .map(item =
      questionCard({
        id: item.question_id,
        title: item.title,
      })
    )
    .join('&<#39;);
  const que>stions = `div id<=&qu>ot;questions"${questionCards}/div`;
  return title + form + questions;
}

Fungsi template ini adalah JavaScript murni, dan berguna untuk memecah logika menjadi fungsi pembantu yang lebih kecil jika diperlukan. Di sini, saya meneruskan setiap item yang ditampilkan dalam respons API ke salah satu fungsi tersebut, yang membuat elemen HTML standar dengan semua atribut yang sesuai ditetapkan.

function questionCard({id, title}) {
  return `<a class="card"
             href="/questions/${id}"
             data-cache-url=>"${<qu>estionUrl(id)}"${title}/a`;
}

Yang perlu diperhatikan adalah atribut data yang saya tambahkan ke setiap link, data-cache-url, yang ditetapkan ke URL Stack Exchange API yang saya butuhkan untuk menampilkan pertanyaan yang sesuai. Ingatlah hal itu. Saya akan meninjaunya lagi nanti.

Kembali ke route handler, setelah pembuatan template selesai, saya melakukan streaming bagian akhir HTML halaman ke browser, dan mengakhiri streaming. Ini adalah isyarat ke browser bahwa rendering progresif telah selesai.

app.get(routes.get('index'), async (req>, res) = {
  res.write(headPartial + navbarPartial);
  const tag = req.query.tag || DEFAULT_TAG;
  const data = await requestData(...);
  res.write(templates.index(tag, data.items));
  res.write(footPartial);
  res.end();
});

Jadi, itulah tur singkat tentang penyiapan server saya. Pengguna yang mengunjungi aplikasi web saya untuk pertama kalinya akan selalu mendapatkan respons dari server, tetapi saat pengunjung kembali ke aplikasi web saya, service worker saya akan mulai merespons. Mari kita bahas lebih lanjut.

Service worker

Ringkasan pembuatan respons navigasi, di pekerja layanan.

Diagram ini mungkin sudah tidak asing bagi Anda—banyak bagian yang sama yang telah saya bahas sebelumnya ada di sini dalam susunan yang sedikit berbeda. Mari kita bahas alur permintaan, dengan mempertimbangkan pekerja layanan.

Service worker kami menangani permintaan navigasi yang masuk untuk URL tertentu, dan seperti yang dilakukan server saya, service worker ini menggunakan kombinasi logika perutean dan pembuatan template untuk mengetahui cara merespons.

Pendekatannya sama seperti sebelumnya, tetapi dengan primitif tingkat rendah yang berbeda, seperti fetch() dan Cache Storage API. Saya menggunakan sumber data tersebut untuk membuat respons HTML, yang diteruskan kembali oleh pekerja layanan ke aplikasi web.

Workbox

Daripada memulai dari awal dengan primitif tingkat rendah, saya akan membangun service worker di atas serangkaian library tingkat tinggi yang disebut Workbox. Library ini memberikan fondasi yang kuat untuk logika pembuatan respons, perutean, dan penyimpanan dalam cache pekerja layanan.

Pemilihan rute

Sama seperti kode sisi server saya, pekerja layanan saya perlu mengetahui cara mencocokkan permintaan masuk dengan logika respons yang sesuai.

Pendekatan saya adalah menerjemahkan setiap rute Express ke ekspresi reguler yang sesuai, dengan memanfaatkan library berguna yang disebut regexparam. Setelah terjemahan tersebut dilakukan, saya dapat memanfaatkan dukungan bawaan Workbox untuk perutean ekspresi reguler.

Setelah mengimpor modul yang memiliki ekspresi reguler, saya mendaftarkan setiap ekspresi reguler dengan perute Workbox. Di dalam setiap rute, saya dapat menyediakan logika pembuatan template kustom untuk menghasilkan respons. Pembuatan template di pekerja layanan sedikit lebih rumit daripada di server backend saya, tetapi Workbox membantu banyak hal yang berat.

import regExpRoutes from './regexp-routes.mjs';

workbox.routing.registerRoute(
  regExpRoutes.get('index')
  // Templating logic.
);

Caching aset statis

Salah satu bagian penting dari cerita pembuatan template adalah memastikan bahwa template HTML parsial saya tersedia secara lokal melalui Cache Storage API, dan terus diperbarui saat saya men-deploy perubahan ke aplikasi web. Pemeliharaan cache dapat menimbulkan kesalahan jika dilakukan secara manual, jadi saya menggunakan Workbox untuk menangani pra-pengambilan sebagai bagian dari proses build saya.

Saya memberi tahu Workbox URL mana yang akan di-precache menggunakan file konfigurasi, yang mengarah ke direktori yang berisi semua aset lokal saya beserta serangkaian pola yang cocok. File ini dibaca secara otomatis oleh CLI Workbox, yang dijalankan setiap kali saya membangun ulang situs.

module.exports = {
  globDirectory: 'build',
  globPatterns: ['**/*.{html,js,svg}'],
  // Other options...
};

Workbox mengambil snapshot konten setiap file, dan secara otomatis menyuntikkan daftar URL dan revisi tersebut ke dalam file service worker akhir saya. Workbox kini memiliki semua yang diperlukan untuk membuat file yang di-cache sebelumnya selalu tersedia, dan selalu diperbarui. Hasilnya adalah file service-worker.js yang berisi sesuatu yang mirip dengan berikut:

workbox.precaching.precacheAndRoute([
  {
    url: 'partials/about.html',
    revision: '518747aad9d7e',
  },
  {
    url: 'partials/foot.html',
    revision: '69bf746a9ecc6',
  },
  // etc.
]);

Untuk orang-orang yang menggunakan proses build yang lebih kompleks, Workbox memiliki plugin webpack dan modul node generik, selain antarmuka command line.

Streaming

Selanjutnya, saya ingin pekerja layanan melakukan streaming HTML parsial yang telah di-cache sebelumnya kembali ke aplikasi web dengan segera. Hal ini merupakan bagian penting dari "cepat dan andal"—saya selalu mendapatkan sesuatu yang bermakna di layar dengan segera. Untungnya, penggunaan Streams API dalam service worker kami memungkinkan hal itu.

Sekarang, Anda mungkin pernah mendengar tentang Streams API sebelumnya. Rekan saya, Jake Archibald, telah memujinya selama bertahun-tahun. Dia membuat prediksi berani bahwa 2016 akan menjadi tahun streaming web. Dan Streams API sama hebatnya hari ini seperti dua tahun lalu, tetapi dengan perbedaan penting.

Meskipun saat itu hanya Chrome yang mendukung Streams, Streams API kini didukung secara lebih luas. Secara keseluruhan, ceritanya positif, dan dengan kode penggantian yang tepat, tidak ada yang menghalangi Anda menggunakan streaming di pekerja layanan Anda saat ini.

Nah... mungkin ada satu hal yang menghentikan Anda, yaitu memahami cara kerja Streams API. API ini mengekspos serangkaian primitif yang sangat canggih, dan developer yang terbiasa menggunakannya dapat membuat alur data yang kompleks, seperti berikut:

const stream = new ReadableStream({
  pull(controller) {
    return sources[0]
      .then(r => r.read())
      .then(result => {
        if (result.done) {
          sources.shift();
          if (sources.length === 0) return controller.close();
          return this.pull(controller);
        } else {
          controller.enqueue(result.value);
        }
      });
  },
});

Namun, memahami implikasi lengkap dari kode ini mungkin tidak cocok untuk semua orang. Daripada mengurai logika ini, mari kita bahas pendekatan saya terhadap streaming pekerja layanan.

Saya menggunakan wrapper tingkat tinggi yang baru, workbox-streams. Dengan begitu, saya dapat meneruskannya dalam campuran sumber streaming, baik dari cache maupun data runtime yang mungkin berasal dari jaringan. Workbox menangani koordinasi setiap sumber dan menggabungkannya menjadi satu respons streaming.

Selain itu, Workbox otomatis mendeteksi apakah Streams API didukung, dan jika tidak, Workbox akan membuat respons non-streaming yang setara. Artinya, Anda tidak perlu khawatir menulis penggantian, karena streaming akan semakin mendekati dukungan browser 100%.

Penyimpanan dalam cache saat runtime

Mari kita lihat cara service worker saya menangani data runtime, dari Stack Exchange API. Saya memanfaatkan dukungan bawaan Workbox untuk strategi caching basi saat divalidasi ulang, beserta masa berlaku untuk memastikan penyimpanan aplikasi web tidak tumbuh tanpa batas.

Saya menyiapkan dua strategi di Workbox untuk menangani berbagai sumber yang akan membentuk respons streaming. Dalam beberapa panggilan fungsi dan konfigurasi, Workbox memungkinkan kita melakukan hal yang biasanya memerlukan ratusan baris kode yang ditulis tangan.

const cacheStrategy = workbox.strategies.cacheFirst({
  cacheName: workbox.core.cacheNames.precache,
});

const apiStrategy = workbox.strategies.staleWhileRevalidate({
  cacheName: API_CACHE_NAME,
  plugins: [new workbox.expiration.Plugin({maxEntries: 50})],
});

Strategi pertama membaca data yang telah di-pra-cache, seperti template HTML parsial kami.

Strategi lainnya menerapkan logika caching stale-while-revalidate, bersama dengan masa berlaku cache least-recently-used setelah kita mencapai 50 entri.

Setelah menerapkan strategi tersebut, yang perlu dilakukan adalah memberi tahu Workbox cara menggunakannya untuk membuat respons streaming yang lengkap. Saya meneruskan array sumber sebagai fungsi, dan setiap fungsi tersebut akan dieksekusi segera. Workbox mengambil hasil dari setiap sumber dan mengalirkan hasil tersebut ke aplikasi web secara berurutan, hanya menunda jika fungsi berikutnya dalam array belum selesai.

workbox.streams.strategy([
  () => cacheStrategy.makeRequest({request: '/head.html'})>,
  () = cacheStrategy.makeRequest({request: '/navbar.html'}),
  async >({event, url}) = {
    const tag = url.searchParams.get('tag') || DEFAULT_TAG;
    const listResponse = await apiStrategy.makeRequest(...);
    const data = await listResponse.json();
    return templates.index(tag, >data.items);
  },
  () = cacheStrategy.makeRequest({request: '/foot.html'}),
]);

Dua sumber pertama adalah template parsial yang di-precache dan dibaca langsung dari Cache Storage API, sehingga akan selalu tersedia dengan segera. Hal ini memastikan bahwa penerapan pekerja layanan kami akan merespons permintaan dengan cepat dan andal, seperti kode sisi server saya.

Fungsi sumber berikutnya mengambil data dari Stack Exchange API, dan memproses respons menjadi HTML yang diharapkan oleh aplikasi web.

Strategi stale-while-revalidate berarti jika saya memiliki respons yang di-cache sebelumnya untuk panggilan API ini, saya akan dapat melakukan streaming ke halaman dengan segera, sambil memperbarui entri cache "di latar belakang" untuk waktu berikutnya saat diminta.

Terakhir, saya melakukan streaming salinan footer yang di-cache dan menutup tag HTML akhir, untuk menyelesaikan respons.

Berbagi kode membuat semuanya tetap sinkron

Anda akan melihat bahwa beberapa bagian kode pekerja layanan terlihat familiar. HTML parsial dan logika template yang digunakan oleh pekerja layanan saya identik dengan yang digunakan oleh handler sisi server saya. Berbagi kode ini memastikan bahwa pengguna mendapatkan pengalaman yang konsisten, baik saat mengunjungi aplikasi web saya untuk pertama kalinya maupun saat kembali ke halaman yang dirender oleh pekerja layanan. Itulah keunggulan JavaScript isomorfik.

Peningkatan dinamis dan progresif

Saya telah membahas server dan pekerja layanan untuk PWA saya, tetapi ada satu logika terakhir yang perlu dibahas: ada sejumlah kecil JavaScript yang berjalan di setiap halaman saya, setelah halaman tersebut sepenuhnya di-streaming.

Kode ini secara progresif meningkatkan pengalaman pengguna, tetapi tidak penting—aplikasi web akan tetap berfungsi jika tidak dijalankan.

Metadata halaman

Aplikasi saya menggunakan JavaScript sisi klien untuk memperbarui metadata halaman berdasarkan respons API. Karena saya menggunakan bit awal HTML yang di-cache yang sama untuk setiap halaman, aplikasi web akhirnya memiliki tag generik di head dokumen saya. Namun, melalui koordinasi antara kode sisi klien dan template, saya dapat memperbarui judul jendela menggunakan metadata spesifik per halaman.

Sebagai bagian dari kode pembuatan template, pendekatan saya adalah menyertakan tag skrip yang berisi string yang di-escape dengan benar.

const metadataScript = `<script>
  self._title = '${escape(item.title)<}';>
/script`;

Kemudian, setelah halaman dimuat, saya membaca string tersebut dan memperbarui judul dokumen.

if (self._title) {
  document.title = unescape(self._title);
}

Jika ada bagian metadata spesifik per halaman lainnya yang ingin Anda perbarui di aplikasi web Anda sendiri, Anda dapat mengikuti pendekatan yang sama.

UX Offline

Peningkatan progresif lainnya yang telah saya tambahkan digunakan untuk menarik perhatian pada kemampuan offline kami. Saya telah membuat PWA yang andal, dan saya ingin pengguna tahu bahwa saat offline, mereka tetap dapat memuat halaman yang sebelumnya dikunjungi.

Pertama, saya menggunakan Cache Storage API untuk mendapatkan daftar semua permintaan API yang di-cache sebelumnya, dan saya menerjemahkannya menjadi daftar URL.

Ingat atribut data khusus yang saya sebutkan, yang masing-masing berisi URL untuk permintaan API yang diperlukan untuk menampilkan pertanyaan? Saya dapat membandingkan silang atribut data tersebut dengan daftar URL yang di-cache, dan membuat array dari semua link pertanyaan yang tidak cocok.

Saat browser memasuki status offline, I loop through daftar link yang tidak di-cache, dan meredupkan link yang tidak akan berfungsi. Perlu diingat bahwa ini hanyalah petunjuk visual kepada pengguna tentang apa yang harus mereka harapkan dari halaman tersebut—saya tidak benar-benar menonaktifkan link, atau mencegah pengguna berpindah ke halaman lain.

const apiCache = await caches.open(API_CACHE_NAME);
const cachedRequests = await apiCache.keys();
const cachedUrls = cachedRequests.map(request => request.url);

const cards = document.querySelectorAll('.card');
const uncachedCards = [...cards].filte>r(card = {
  return !cachedUrls.includes(card.dataset.cacheUrl);
});

const offlineHandle>r = () = {
  for (const uncachedCard of uncachedCards) {
    uncachedCard.style.opacity = '0.3';
  }
};

const onli>neHandler = () = {
  for (const uncachedCard of uncachedCards) {
    uncachedCard.style.opacity = '1.0';
  }
};

window.addEventListener('online', onlineHandler);
window.addEventListener('offline', offlineHandler);

Kesalahan umum

Sekarang saya telah menjelaskan pendekatan saya dalam membangun PWA multi-halaman. Ada banyak faktor yang harus Anda pertimbangkan saat menyusun pendekatan Anda sendiri, dan Anda mungkin membuat pilihan yang berbeda dari yang saya buat. Fleksibilitas tersebut adalah salah satu hal menarik tentang pengembangan untuk web.

Ada beberapa kesalahan umum yang mungkin Anda temui saat membuat keputusan arsitektur sendiri, dan saya ingin menyelamatkan Anda dari beberapa masalah.

Jangan menyimpan cache HTML lengkap

Sebaiknya jangan menyimpan dokumen HTML lengkap di cache Anda. Pertama, itu membuang-buang ruang. Jika aplikasi web Anda menggunakan struktur HTML dasar yang sama untuk setiap halamannya, Anda akan menyimpan salinan markup yang sama berulang kali.

Lebih penting lagi, jika Anda men-deploy perubahan pada struktur HTML bersama situs Anda, setiap halaman yang di-cache sebelumnya masih menggunakan tata letak lama Anda. Bayangkan frustrasinya pengunjung yang kembali melihat campuran halaman lama dan baru.

Penyimpangan server / service worker

Kesalahan lain yang harus dihindari adalah server dan pekerja layanan Anda tidak tersinkron. Pendekatan saya adalah menggunakan JavaScript isomorfik, sehingga kode yang sama dijalankan di kedua tempat. Bergantung pada arsitektur server yang ada, hal itu tidak selalu memungkinkan.

Apa pun keputusan arsitektur yang Anda buat, Anda harus memiliki beberapa strategi untuk menjalankan kode perutean dan pembuatan template yang setara di server dan pekerja layanan Anda.

Skenario terburuk

Tata letak / desain yang tidak konsisten

Apa yang terjadi jika Anda mengabaikan kesalahan tersebut? Nah, segala jenis kegagalan dapat terjadi, tetapi skenario terburuknya adalah pengguna yang kembali mengunjungi halaman yang di-cache dengan tata letak yang sangat usang—mungkin halaman dengan teks header yang sudah tidak berlaku, atau yang menggunakan nama class CSS yang tidak lagi valid.

Skenario terburuk: Perutean rusak

Atau, pengguna mungkin menemukan URL yang ditangani oleh server Anda, tetapi bukan pekerja layanan Anda. Situs yang penuh dengan tata letak zombie dan jalan buntu bukanlah PWA yang andal.

Tips untuk meraih sukses

Namun, Anda tidak sendiri dalam hal ini. Tips berikut dapat membantu Anda menghindari kesalahan tersebut:

Gunakan library perutean dan pembuatan template yang memiliki penerapan multi-bahasa

Coba gunakan library perutean dan pembuatan template yang memiliki penerapan JavaScript. Sekarang, saya tahu bahwa tidak semua developer memiliki kemewahan untuk bermigrasi dari server web dan bahasa template saat ini.

Namun, sejumlah framework perutean dan pembuatan template populer memiliki penerapan dalam beberapa bahasa. Jika Anda dapat menemukan salah satu yang berfungsi dengan JavaScript serta bahasa server saat ini, Anda selangkah lebih dekat untuk menjaga sinkronisasi server dan pekerja layanan.

Lebih memilih template berurutan daripada template bertingkat

Selanjutnya, sebaiknya gunakan serangkaian template berurutan yang dapat di-streaming satu demi satu. Tidak masalah jika bagian selanjutnya dari halaman Anda menggunakan logika template yang lebih rumit, asalkan Anda dapat melakukan streaming bagian awal HTML secepat mungkin.

Menyimpan konten statis dan dinamis di pekerja layanan Anda

Untuk performa terbaik, Anda harus melakukan pra-cache semua resource statis penting situs Anda. Anda juga harus menyiapkan logika caching runtime untuk menangani konten dinamis, seperti permintaan API. Dengan menggunakan Workbox, Anda dapat membangun strategi yang telah diuji dengan baik dan siap produksi, alih-alih menerapkan semuanya dari awal.

Hanya memblokir di jaringan jika benar-benar diperlukan

Selain itu, Anda hanya boleh memblokir jaringan jika tidak memungkinkan untuk melakukan streaming respons dari cache. Menampilkan respons API yang di-cache secara langsung sering kali menghasilkan pengalaman pengguna yang lebih baik daripada menunggu data baru.

Resource