Meningkatkan Progressive Web App Anda secara bertahap

Membangun untuk browser modern dan meningkatkan kualitasnya secara bertahap seperti pada tahun 2003

Pada bulan Maret 2003, Nick Finck dan Steve Champeon mengejutkan dunia desain web dengan konsep progressive enhancement, strategi untuk desain web yang menekankan pemuatan konten laman web inti terlebih dahulu, dan kemudian secara progresif menambahkan serta presentasi dan fitur yang sangat ketat di atas konten. Pada tahun 2003, {i>progressive enhancement <i} adalah tentang penggunaan fitur CSS, JavaScript yang tidak mengganggu, dan bahkan hanya Scalable Vector Graphics. {i>Progressive enhancement<i} pada tahun 2020 dan seterusnya adalah tentang menggunakan kemampuan browser modern.

Desain web inklusif untuk masa depan dengan progressive enhancement. Slide judul dari presentasi asli Finck dan Champeon.
Slide: Desain Web Inklusif untuk Masa Depan Dengan Peningkatan Progresif. (Sumber)

JavaScript Modern

Berbicara tentang JavaScript, browser mendukung situasi untuk JavaScript inti ES 2015 terbaru fitur tersebut sangat bagus. Standar baru ini mencakup promise, modul, class, literal template, fungsi panah, let dan const, parameter default, generator, penetapan destrukturisasi, istirahat dan sebar, Map/Set, WeakMap/WeakSet, dan banyak lagi. Semua didukung.

Tabel dukungan CanIUse untuk fitur ES6 yang menampilkan dukungan di semua browser utama.
Tabel dukungan browser ECMAScript 2015 (ES6). (Sumber)

Fungsi asinkron, fitur ES 2017 dan salah satu favorit saya, dapat digunakan di semua {i>browser<i} terkemuka. Kata kunci async dan await memungkinkan perilaku berbasis promise yang asinkron ditulis dengan gaya yang lebih rapi, sehingga tidak perlu mengonfigurasi rantai promise secara eksplisit.

Tabel dukungan CanIUse untuk fungsi asinkron yang menunjukkan dukungan di semua browser utama.
Tabel dukungan browser fungsi Asinkron. (Sumber)

Dan bahkan penambahan bahasa ES 2020 yang sangat baru seperti perantaian opsional dan penggabungan nullish telah mendapatkan dukungan dengan sangat cepat. Anda dapat melihat contoh kode di bawah. Dalam hal fitur JavaScript inti, rumputnya tidak lebih hijau dari itu adalah hari ini.

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
Gambar latar belakang rumput hijau Windows XP yang ikonis.
Jika menyangkut fitur inti JavaScript, rumputnya berwarna hijau. (tangkapan layar produk Microsoft, digunakan dengan izin.)

Aplikasi contoh: Fugu Greetings

Untuk artikel ini, saya bekerja dengan PWA sederhana, yang disebut Sapaan Fugu (GitHub). Nama aplikasi ini adalah ujung topi untuk Project Fugu 🐡, sebuah upaya untuk memberikan semua informasi pada web kecanggihan aplikasi Android/iOS/desktop. Anda dapat membaca lebih lanjut tentang proyek ini di halaman landing Anda.

Fugu Salam adalah aplikasi menggambar yang memungkinkan Anda membuat kartu ucapan virtual, dan mengirim orang-orang terkasih. Ini mencontohkan Konsep inti PWA. Penting andal dan sepenuhnya offline diaktifkan, jadi meskipun Anda tidak memiliki jaringan, Anda masih dapat menggunakannya. File ini juga Dapat diinstal ke layar beranda perangkat dan terintegrasi secara mulus dengan sistem operasi sebagai aplikasi yang berdiri sendiri.

Fugu Greetings PWA dengan gambar yang menyerupai logo komunitas PWA.
Aplikasi contoh Fugu Greetings.

{i>Progressive enhancement <i}

Sekarang, saatnya membahas tentang progressive enhancement. Glosarium Dokumen Web MDN mendefinisikan konsep sebagai berikut:

{i>Progressive enhancement <i}adalah filosofi desain yang memberikan dasar dari konten dan fungsionalitas penting kepada sebanyak mungkin pengguna, sementara yang memberikan pengalaman terbaik hanya kepada pengguna perangkat {i>browser<i} yang dapat menjalankan semua kode yang dibutuhkan.

Deteksi fitur umumnya digunakan untuk menentukan apakah {i>browser<i} dapat menangani fungsionalitas yang lebih modern, sedangkan polyfill sering digunakan untuk menambahkan fitur yang hilang dengan JavaScript.

[…]

{i>Progressive enhancement<i} adalah teknik bermanfaat yang memungkinkan pengembang web untuk fokus dalam mengembangkan {i>website<i} terbaik sekaligus membuat situs web tersebut berfungsi pada beberapa agen pengguna yang tidak dikenal. Degradasi halus saling terkait, tetapi tidak sama dan sering dilihat sebagai arah yang berlawanan {i>progressive enhancement<i}. Pada kenyataannya, kedua pendekatan itu valid dan sering kali dapat saling melengkapi.

Kontributor MDN

Memulai setiap kartu ucapan dari awal bisa sangat rumit. Jadi mengapa tidak memiliki fitur yang memungkinkan pengguna untuk mengimpor gambar, dan memulai dari sana? Dengan pendekatan tradisional, Anda akan menggunakan <input type=file> untuk mewujudkannya. Pertama, Anda akan membuat elemen, menetapkan type-nya ke 'file', dan menambahkan jenis MIME ke properti accept, lalu "klik" secara terprogram dan memproses perubahan. Saat Anda memilih sebuah gambar, gambar akan diimpor langsung ke kanvas.

const importImage = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'image/*';
    input.addEventListener('change', () => {
      resolve(input.files[0]);
    });
    input.click();
  });
};

Jika ada fitur impor, kemungkinan harus ada fitur ekspor sehingga pengguna dapat menyimpan kartu ucapan mereka secara lokal. Cara tradisional untuk menyimpan file adalah dengan membuat link anchor dengan download dan dengan URL blob sebagai href-nya. Anda juga akan "mengklik" secara terprogram untuk memicu download, dan, untuk mencegah kebocoran memori, semoga jangan lupa untuk mencabut URL objek blob.

const exportImage = async (blob) => {
  const a = document.createElement('a');
  a.download = 'fugu-greeting.png';
  a.href = URL.createObjectURL(blob);
  a.addEventListener('click', (e) => {
    setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
  });
  a.click();
};

Tapi tunggu sebentar. Secara mental, Anda belum "mengunduh" kartu ucapan, Anda memiliki "disimpan" anotasi. Bukannya menampilkan "simpan" yang memungkinkan Anda memilih tempat untuk meletakkan file, browser telah langsung mendownload kartu ucapan tanpa interaksi pengguna dan memasukkannya langsung ke folder {i>Downloads<i}. Ini tidak bagus.

Bagaimana jika ada cara yang lebih baik? Bagaimana jika Anda bisa membuka {i>file<i} lokal, mengeditnya, lalu menyimpan modifikasinya, ke file baru, atau kembali ke file asli yang awalnya Anda buka? Ternyata ada. File System Access API memungkinkan Anda untuk membuka dan membuat file dan direktori, serta mengubah dan menyimpannya .

Jadi, bagaimana cara mendeteksi fitur API? File System Access API mengekspos metode baru window.chooseFileSystemEntries(). Akibatnya, saya perlu memuat modul impor dan ekspor yang berbeda secara kondisional bergantung pada apakah metode ini tersedia atau tidak. Saya telah menunjukkan cara melakukannya di bawah.

const loadImportAndExport = () => {
  if ('chooseFileSystemEntries' in window) {
    Promise.all([
      import('./import_image.mjs'),
      import('./export_image.mjs'),
    ]);
  } else {
    Promise.all([
      import('./import_image_legacy.mjs'),
      import('./export_image_legacy.mjs'),
    ]);
  }
};

Namun, sebelum saya membahas detail API Akses Sistem File, saya akan menyoroti pola {i>progressive enhancement<i} di sini. Di browser yang saat ini tidak mendukung File System Access API, saya memuat skrip lama. Anda dapat melihat tab jaringan Firefox dan Safari di bawah.

Safari Web Inspector menampilkan file lama yang dimuat.
Tab jaringan Safari Web Inspector.
Developer Tools Firefox menampilkan file lama yang dimuat.
Tab jaringan Alat Developer Firefox.

Namun, di Chrome, browser yang mendukung API, hanya skrip baru yang dimuat. Hal ini dapat dilakukan dengan elegan berkat import() dinamis, yang digunakan oleh semua browser modern dukungan. Seperti kataku sebelumnya, rumputnya cukup hijau sekarang.

Chrome DevTools menampilkan file modern yang dimuat.
Tab jaringan Chrome DevTools.

File System Access API

Setelah saya mengatasi masalah ini, kini saatnya melihat implementasi sebenarnya berdasarkan File System Access API. Untuk mengimpor gambar, saya memanggil window.chooseFileSystemEntries() dan meneruskan properti accepts tempat saya mengatakan saya ingin file gambar. Baik ekstensi file maupun jenis MIME didukung. Tindakan ini menghasilkan handle file, yang memungkinkan saya mendapatkan file sebenarnya dengan memanggil getFile().

const importImage = async () => {
  try {
    const handle = await window.chooseFileSystemEntries({
      accepts: [
        {
          description: 'Image files',
          mimeTypes: ['image/*'],
          extensions: ['jpg', 'jpeg', 'png', 'webp', 'svg'],
        },
      ],
    });
    return handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

Mengekspor gambar hampir sama, tetapi kali ini Saya perlu meneruskan parameter jenis 'save-file' ke metode chooseFileSystemEntries(). Dari sini saya mendapatkan dialog penyimpanan file. Dengan file terbuka, ini tidak diperlukan karena 'open-file' adalah default-nya. Saya menyetel parameter accepts seperti sebelumnya, tetapi kali ini terbatas hanya untuk gambar PNG. Sekali lagi saya mendapatkan kembali {i>handle<i} file, tetapi alih-alih mendapatkan file tersebut, kali ini saya membuat streaming yang dapat ditulis dengan memanggil createWritable(). Selanjutnya, saya menulis blob, yang merupakan gambar kartu ucapan saya, ke file tersebut. Terakhir, saya menutup streaming yang dapat ditulis.

Semuanya selalu bisa gagal: {i>disk<i} bisa jadi kehabisan ruang, mungkin ada kesalahan tulis atau baca, atau mungkin pengguna membatalkan dialog file. Inilah sebabnya saya selalu menggabungkan panggilan dalam pernyataan try...catch.

const exportImage = async (blob) => {
  try {
    const handle = await window.chooseFileSystemEntries({
      type: 'save-file',
      accepts: [
        {
          description: 'Image file',
          extensions: ['png'],
          mimeTypes: ['image/png'],
        },
      ],
    });
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

Menggunakan {i>progressive enhancement<i} dengan API Akses Sistem File, Saya dapat membuka file seperti sebelumnya. File yang diimpor akan digambar langsung di kanvas. Saya dapat melakukan pengeditan dan akhirnya menyimpannya dengan kotak dialog "Save" di mana saya dapat memilih nama dan lokasi penyimpanan file. Sekarang file siap untuk dipertahankan selamanya.

Aplikasi Fugu Greetings dengan dialog pembuka file.
Dialog pembukaan file.
Aplikasi Fugu Greetings sekarang dengan gambar yang diimpor.
Gambar yang diimpor.
Aplikasi Fugu Greetings dengan gambar yang dimodifikasi.
Menyimpan gambar yang telah diubah ke file baru.

Web Share dan Web Share Target API

Selain menyimpan untuk selamanya, mungkin saya sebenarnya ingin berbagi kartu ucapan saya. Ini adalah sesuatu yang tidak diketahui oleh Web Share API Web Share Target API memungkinkan saya melakukannya. Sistem operasi seluler, dan yang terbaru pada desktop telah dilengkapi fitur berbagi bawaan mekanisme atensi. Misalnya, di bawah ini adalah sheet berbagi Safari desktop di macOS yang dipicu dari artikel di blog saya. Saat Anda mengklik tombol Bagikan Artikel, Anda dapat membagikan link ke artikel tersebut kepada teman, untuk contoh, melalui aplikasi Message macOS.

Sheet berbagi Desktop Safari di macOS dipicu dari tombol Bagikan di artikel
Web Share API di Safari desktop di macOS.

Kode untuk melakukannya cukup mudah. Saya memanggil navigator.share() dan meneruskan title, text, dan url opsional dalam sebuah objek. Namun, bagaimana jika saya ingin melampirkan gambar? Level 1 dari Web Share API belum mendukung fitur ini. Kabar baiknya adalah Web Share Level 2 telah menambahkan kemampuan berbagi file.

try {
  await navigator.share({
    title: 'Check out this article:',
    text: `"${document.title}" by @tomayac:`,
    url: document.querySelector('link[rel=canonical]').href,
  });
} catch (err) {
  console.warn(err.name, err.message);
}

Saya akan menunjukkan cara membuat aplikasi ini berfungsi dengan aplikasi kartu Salam Fugu. Pertama, saya perlu menyiapkan objek data dengan array files yang terdiri dari satu blob, lalu title dan text. Selanjutnya, sebagai praktik terbaik, saya menggunakan metode navigator.canShare() baru yang melakukan sesuai dengan namanya: Ini memberi tahu saya apakah objek data yang saya coba bagikan secara teknis dapat dibagikan oleh browser. Jika navigator.canShare() memberi tahu saya bahwa data dapat dibagikan, saya siap untuk panggil navigator.share() seperti sebelumnya. Karena semuanya bisa gagal, saya kembali menggunakan blok try...catch.

const share = async (title, text, blob) => {
  const data = {
    files: [
      new File([blob], 'fugu-greeting.png', {
        type: blob.type,
      }),
    ],
    title: title,
    text: text,
  };
  try {
    if (!(navigator.canShare(data))) {
      throw new Error("Can't share data.", data);
    }
    await navigator.share(data);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

Seperti sebelumnya, saya menggunakan {i>progressive enhancement<i}. Jika 'share' dan 'canShare' ada pada objek navigator, hanya saya akan melanjutkan dan memuat share.mjs melalui import() dinamis. Pada {i>browser<i} seperti Safari seluler yang hanya memenuhi salah satu dari dua syarat, saya tidak memuat fungsionalitasnya.

const loadShare = () => {
  if ('share' in navigator && 'canShare' in navigator) {
    import('./share.mjs');
  }
};

Di Fugu Salam, jika saya mengetuk tombol Share pada browser pendukung seperti Chrome di Android, {i>sheet<i} berbagi bawaan akan terbuka. Saya dapat, misalnya, memilih Gmail, dan widget pembuat email muncul dengan gambar dilampirkan.

Sheet berbagi tingkat OS yang menampilkan berbagai aplikasi untuk berbagi gambar.
Memilih aplikasi yang akan dijadikan tujuan berbagi file.
Widget penulisan email Gmail dengan gambar terlampir.
File dilampirkan ke email baru di komposer Gmail.

Contact Picker API

Selanjutnya, saya ingin bicara tentang kontak, artinya buku alamat perangkat atau aplikasi pengelola kontak. Saat Anda menulis kartu ucapan, mungkin tidak selalu mudah untuk menulis dengan benar nama seseorang. Misalnya, saya punya teman Sergey yang lebih suka namanya dieja dalam huruf Sirilik. Saya adalah menggunakan {i>keyboard<i} QWERTZ Jerman dan tidak tahu cara mengetik namanya. Ini adalah masalah yang dapat diselesaikan oleh Contact Picker API. Karena teman saya menyimpan aplikasi kontak di ponsel saya, melalui Contacts Picker API, saya dapat mengakses kontak dari web.

Pertama, saya perlu menentukan daftar properti yang ingin saya akses. Dalam hal ini, saya hanya ingin nama, tetapi untuk kasus penggunaan lainnya, saya mungkin tertarik dengan nomor telepon, email, avatar ikon, atau alamat fisik. Selanjutnya, saya mengonfigurasi objek options dan menetapkan multiple ke true, sehingga saya dapat memilih lebih banyak dari satu entri. Terakhir, saya dapat memanggil navigator.contacts.select() yang menampilkan properti yang diinginkan untuk kontak pilihan pengguna.

const getContacts = async () => {
  const properties = ['name'];
  const options = { multiple: true };
  try {
    return await navigator.contacts.select(properties, options);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

Dan sekarang Anda mungkin telah mempelajari polanya: Saya hanya memuat file jika API benar-benar didukung.

if ('contacts' in navigator) {
  import('./contacts.mjs');
}

Di Salam Fugu, saat saya mengetuk tombol Kontak dan memilih dua sahabat, Cеррей 佋拇矅佋奇 b Menitip dan 劳伦斯·爱德华·"拉里"·佩奇, Anda dapat melihat bagaimana pemilih kontak dibatasi agar hanya menampilkan nama mereka, alamat email, atau informasi lain seperti nomor telepon mereka. Nama-nama mereka kemudian digambar di kartu ucapan saya.

Pemilih kontak yang menampilkan nama dua kontak di buku alamat.
Memilih dua nama dengan pemilih kontak dari buku alamat.
Nama kedua kontak yang dipilih sebelumnya, digambar di kartu ucapan.
Kedua nama tersebut kemudian ditulis di kartu ucapan.

API Papan Klip Asinkron

Selanjutnya adalah menyalin dan menempel. Salah satu kegiatan favorit kami sebagai pengembang perangkat lunak adalah {i>copy<i} dan {i>paste<i}. Sebagai penulis kartu ucapan, terkadang saya mungkin ingin melakukan hal yang sama. Saya mungkin ingin menempelkan gambar ke kartu ucapan yang sedang saya kerjakan, atau menyalin kartu ucapan sehingga saya dapat melanjutkan pengeditannya dari di tempat lain. Asynchronous Clipboard API, mendukung teks dan gambar. Saya akan menjelaskan cara menambahkan dukungan salin dan tempel ke Fugu Aplikasi Salam.

Untuk menyalin sesuatu ke {i>clipboard<i} sistem, saya harus menulisnya. Metode navigator.clipboard.write() mengambil array item papan klip sebagai . Setiap item papan klip pada dasarnya adalah objek dengan blob sebagai nilai, dan jenis blob sebagai kuncinya.

const copy = async (blob) => {
  try {
    await navigator.clipboard.write([
      new ClipboardItem({
        [blob.type]: blob,
      }),
    ]);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

Untuk menempel, saya perlu mengulang item papan klip yang saya peroleh dengan memanggil navigator.clipboard.read(). Alasannya adalah beberapa item {i>clipboard<i} mungkin berada pada {i>clipboard<i} di representasi yang berbeda. Setiap item papan klip memiliki kolom types yang memberi tahu saya jenis MIME yang tersedia Google Cloud Platform. Saya memanggil metode getType() item papan klip, yang meneruskan Jenis MIME yang saya peroleh sebelumnya.

const paste = async () => {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      try {
        for (const type of clipboardItem.types) {
          const blob = await clipboardItem.getType(type);
          return blob;
        }
      } catch (err) {
        console.error(err.name, err.message);
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
};

Dan sekarang hampir tidak perlu untuk dikatakan. Saya hanya melakukannya di browser pendukung.

if ('clipboard' in navigator && 'write' in navigator.clipboard) {
  import('./clipboard.mjs');
}

Jadi bagaimana cara kerjanya? Saya membuka gambar di aplikasi Pratinjau macOS dan menyalinnya ke {i>clipboard<i}. Saat mengklik Tempel, aplikasi Salam Fugu akan bertanya apakah saya ingin mengizinkan aplikasi melihat teks dan gambar di {i>clipboard<i}.

Aplikasi Fugu Greetings menampilkan prompt izin papan klip.
Dialog izin papan klip.

Terakhir, setelah menerima izin tersebut, image akan ditempelkan ke aplikasi. Hal sebaliknya juga berlaku. Saya akan menyalin kartu ucapan ke {i>clipboard<i}. Saat saya membuka Preview dan mengklik File, lalu New from Clipboard, kartu ucapan ditempelkan ke gambar baru tanpa judul.

Aplikasi Pratinjau macOS dengan gambar tanpa judul yang hanya ditempelkan.
Gambar ditempelkan ke aplikasi Pratinjau macOS.

Badging API

API berguna lainnya adalah Badging API. Sebagai PWA yang dapat diinstal, Fugu Greetings memang memiliki ikon aplikasi yang dapat ditempatkan pengguna di dok aplikasi atau layar beranda. Cara yang menyenangkan dan mudah untuk mendemonstrasikan API adalah dengan (ab) menggunakannya dalam Salam Fugu sebagai penghitung pena. Saya telah menambahkan pemroses peristiwa yang menambah penghitung goresan pena setiap kali peristiwa pointerdown terjadi kemudian menyetel lencana ikon yang diperbarui. Setiap kali kanvas dibersihkan, penghitung akan direset, dan badge dihapus.

let strokes = 0;

canvas.addEventListener('pointerdown', () => {
  navigator.setAppBadge(++strokes);
});

clearButton.addEventListener('click', () => {
  strokes = 0;
  navigator.setAppBadge(strokes);
});

Fitur ini adalah progressive enhancement, jadi logika pemuatan seperti biasa.

if ('setAppBadge' in navigator) {
  import('./badge.mjs');
}

Dalam contoh ini, saya telah menggambar angka dari satu sampai tujuh, menggunakan satu goresan pena dengan ukuran unit maksimum 1 MB per angka. Penghitung badge di ikon sekarang mencapai tujuh.

Angka dari satu sampai tujuh digambar di kartu ucapan, masing-masing hanya dengan satu goresan pena.
Menggambar angka dari 1 sampai 7, menggunakan tujuh sapuan pena.
Ikon badge di aplikasi Fugu Greetings yang menampilkan angka 7.
Penghitung goresan pena dalam bentuk badge ikon aplikasi.

Periodic Background Sync API

Ingin memulai hari baru dengan hal baru? Fitur yang rapi dari aplikasi Salam Fugu adalah bahwa aplikasi ini dapat menginspirasi Anda setiap pagi dengan gambar latar baru untuk memulai kartu ucapan Anda. Aplikasi menggunakan Periodic Background Sync API untuk mencapai hal tersebut.

Langkah pertama adalah mendaftarkan peristiwa sinkronisasi berkala dalam pendaftaran pekerja layanan. Fungsi ini memproses tag sinkronisasi yang disebut 'image-of-the-day' dan memiliki interval minimum satu hari, sehingga pengguna bisa mendapatkan gambar latar belakang baru setiap 24 jam.

const registerPeriodicBackgroundSync = async () => {
  const registration = await navigator.serviceWorker.ready;
  try {
    registration.periodicSync.register('image-of-the-day-sync', {
      // An interval of one day.
      minInterval: 24 * 60 * 60 * 1000,
    });
  } catch (err) {
    console.error(err.name, err.message);
  }
};

Langkah kedua adalah memproses peristiwa periodicsync di pekerja layanan. Jika tag peristiwa adalah 'image-of-the-day', yaitu tag yang didaftarkan sebelumnya, gambar hari ini diambil melalui fungsi getImageOfTheDay(), dan hasilnya disebarkan ke semua klien, sehingga mereka dapat memperbarui kanvas dan di cache oleh pengguna.

self.addEventListener('periodicsync', (syncEvent) => {
  if (syncEvent.tag === 'image-of-the-day-sync') {
    syncEvent.waitUntil(
      (async () => {
        const blob = await getImageOfTheDay();
        const clients = await self.clients.matchAll();
        clients.forEach((client) => {
          client.postMessage({
            image: blob,
          });
        });
      })()
    );
  }
});

Sekali lagi, ini benar-benar merupakan {i>progressive enhancement<i}, sehingga kode hanya dimuat ketika API didukung oleh browser. Ini berlaku untuk kode klien dan kode pekerja layanan. Pada browser yang tidak mendukung, tidak satu pun yang dimuat. Perhatikan caranya di pekerja layanan, bukan import() dinamis (yang tidak didukung dalam konteks pekerja layanan belum), Saya menggunakan klasik importScripts().

// In the client:
const registration = await navigator.serviceWorker.ready;
if (registration && 'periodicSync' in registration) {
  import('./periodic_background_sync.mjs');
}
// In the service worker:
if ('periodicSync' in self.registration) {
  importScripts('./image_of_the_day.mjs');
}

Di Salam Fugu, menekan tombol Wallpaper akan menampilkan gambar kartu ucapan hari ini yang diperbarui setiap hari melalui Periodic Background Sync API.

Aplikasi Fugu Greetings dengan gambar kartu ucapan baru hari ini.
Menekan tombol Wallpaper akan menampilkan gambar hari ini.

API Pemicu Notifikasi

Terkadang meskipun dengan banyak inspirasi, Anda perlu dorongan untuk menyelesaikan salam yang dimulai . Fitur ini diaktifkan oleh Notification Triggers API. Sebagai pengguna, saya dapat memasukkan kapan saya ingin didorong untuk menyelesaikan kartu ucapan. Jika sudah tiba saatnya, saya akan mendapatkan notifikasi bahwa kartu ucapan saya sedang menunggu.

Setelah meminta waktu target, aplikasi menjadwalkan notifikasi dengan showTrigger. Ini dapat berupa TimestampTrigger dengan tanggal target yang dipilih sebelumnya. Notifikasi pengingat akan dipicu secara lokal, tidak diperlukan jaringan atau sisi server.

const targetDate = promptTargetDate();
if (targetDate) {
  const registration = await navigator.serviceWorker.ready;
  registration.showNotification('Reminder', {
    tag: 'reminder',
    body: "It's time to finish your greeting card!",
    showTrigger: new TimestampTrigger(targetDate),
  });
}

Seperti semua yang telah saya tunjukkan sejauh ini, ini adalah {i>progressive enhancement<i}, sehingga kode hanya dimuat bersyarat.

if ('Notification' in window && 'showTrigger' in Notification.prototype) {
  import('./notification_triggers.mjs');
}

Saat saya mencentang kotak Pengingat di Salam Fugu, sebuah dialog berisi ketika saya ingin diingatkan untuk menyelesaikan kartu ucapan saya.

Aplikasi Fugu Greetings dengan perintah yang menanyakan pengguna kapan mereka ingin diingatkan untuk menyelesaikan kartu ucapan mereka.
Menjadwalkan notifikasi lokal yang akan diingatkan untuk menyelesaikan kartu ucapan.

Ketika notifikasi terjadwal terpicu di Salam Fugu, ini ditampilkan sama seperti notifikasi lainnya, tetapi seperti yang saya tulis sebelumnya, itu tidak memerlukan koneksi jaringan.

Pusat Notifikasi macOS menampilkan notifikasi yang dipicu dari Fugu Greetings.
Notifikasi yang dipicu akan muncul di Pusat Notifikasi macOS.

Wake Lock API

Saya juga ingin menyertakan Wake Lock API. Terkadang Anda hanya perlu menatap layar cukup lama sampai inspirasi mencium Anda. Hal terburuk yang dapat terjadi adalah layar dimatikan. Wake Lock API dapat mencegah hal ini.

Langkah pertama adalah mendapatkan penguncian layar saat aktif dengan navigator.wakelock.request method(). Saya meneruskan string 'screen' ke sini untuk mendapatkan penguncian layar saat aktif. Kemudian saya menambahkan pemroses peristiwa untuk diberi tahu saat penguncian layar saat aktif dilepaskan. Hal ini dapat terjadi, misalnya, saat visibilitas tab berubah. Jika ini terjadi, saya bisa mendapatkan kembali penguncian layar saat aktif saat tab terlihat lagi.

let wakeLock = null;
const requestWakeLock = async () => {
  wakeLock = await navigator.wakeLock.request('screen');
  wakeLock.addEventListener('release', () => {
    console.log('Wake Lock was released');
  });
  console.log('Wake Lock is active');
};

const handleVisibilityChange = () => {
  if (wakeLock !== null && document.visibilityState === 'visible') {
    requestWakeLock();
  }
};

document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener('fullscreenchange', handleVisibilityChange);

Ya, ini adalah peningkatan progresif, jadi saya hanya perlu memuatnya saat browser mendukung API.

if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
  import('./wake_lock.mjs');
}

Di Salam Fugu, ada kotak centang Insomnia yang, jika dicentang, membuat layar menyala.

Kotak centang insomnia, jika dicentang, akan membuat layar tetap menyala.
Kotak centang Insomnia membuat aplikasi tetap aktif.

Idle Detection API

Terkadang, bahkan jika Anda menatap layar selama berjam-jam, itu tidak ada gunanya dan Anda tidak tahu apa-apa yang harus dilakukan dengan kartu ucapan Anda. Idle Detection API memungkinkan aplikasi mendeteksi waktu tidak ada aktivitas pengguna. Jika pengguna tidak ada aktivitas dalam waktu yang terlalu lama, aplikasi akan direset ke status awal dan membersihkan kanvas. API ini saat ini dilindungi di balik izin notifikasi, karena banyak kasus penggunaan deteksi tidak ada aktivitas yang terkait dengan notifikasi, misalnya, untuk hanya mengirim notifikasi ke perangkat yang saat ini aktif digunakan oleh pengguna.

Setelah memastikan bahwa izin notifikasi diberikan, saya kemudian membuat instance pendeteksi tidak ada aktivitas. Saya mendaftarkan pemroses peristiwa yang memproses perubahan tidak ada aktivitas, yang mencakup pengguna dan status layar. Pengguna bisa saja aktif atau tidak ada aktivitas, dan layar dapat dibuka atau dikunci. Jika pengguna tidak ada aktivitas, kanvas akan dihapus. Saya memberikan nilai minimum 60 detik ke detektor yang tidak ada aktivitas.

const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
  const userState = idleDetector.userState;
  const screenState = idleDetector.screenState;
  console.log(`Idle change: ${userState}, ${screenState}.`);
  if (userState === 'idle') {
    clearCanvas();
  }
});

await idleDetector.start({
  threshold: 60000,
  signal,
});

Dan seperti biasa, saya hanya memuat kode ini ketika browser mendukungnya.

if ('IdleDetector' in window) {
  import('./idle_detection.mjs');
}

Di aplikasi Fugu Greetings, kanvas akan dibersihkan jika kotak centang Ephemeral disetel dicentang dan pengguna tidak ada yang bisa berjalan terlalu lama.

Aplikasi Fugu Greetings dengan kanvas yang telah dibersihkan setelah pengguna tidak ada aktivitas terlalu lama.
Jika kotak centang Ephemeral dicentang dan pengguna sudah terlalu lama tidak ada aktivitas, kanvas akan dihapus.

Penutup

Fiuh, perjalanan yang luar biasa. Begitu banyak API hanya dalam satu aplikasi contoh. Dan, ingat, saya tidak pernah membuat pengguna membayar biaya download untuk fitur yang tidak didukung oleh {i>browser<i} mereka. Dengan menggunakan {i>progressive enhancement<i}, saya memastikan bahwa hanya kode relevan yang dimuat. Dan karena dengan HTTP/2, permintaan murah, pola ini akan bekerja dengan baik untuk penerjemahan mesin, meskipun Anda mungkin ingin mempertimbangkan pemaket untuk aplikasi yang sangat besar.

Panel Jaringan Chrome DevTools hanya menampilkan permintaan untuk file dengan kode yang didukung oleh browser saat ini.
Tab Jaringan Chrome DevTools hanya menampilkan permintaan untuk file dengan kode yang didukung oleh browser saat ini.

Aplikasi ini mungkin terlihat sedikit berbeda di setiap {i>browser<i} karena tidak semua platform mendukung semua fitur, namun fungsionalitas intinya selalu ada—ditingkatkan secara progresif sesuai dengan kemampuan browser tertentu. Perhatikan bahwa kemampuan ini dapat berubah bahkan dalam satu {i>browser<i} yang sama, bergantung pada apakah aplikasi berjalan sebagai aplikasi terinstal atau di tab browser.

Salam Fugu yang berjalan di Chrome Android, yang menampilkan banyak fitur yang tersedia.
Fugu Greetings yang berjalan di Android Chrome.
Fugu Greetings yang berjalan di Safari versi desktop, menampilkan lebih sedikit fitur yang tersedia.
Fugu Greetings yang berjalan di Safari desktop.
Fugu Greetings yang berjalan di Chrome desktop, yang menampilkan berbagai fitur yang tersedia.
Fugu Salams berjalan di Chrome desktop.

Jika Anda tertarik dengan aplikasi Fugu Greetings, temukan dan fork di GitHub.

repo Fugu Greetings di GitHub.
Aplikasi Fugu Greetings di GitHub.

Tim Chromium berupaya keras untuk membuat rumput lebih ramah lingkungan terkait API Fugu tingkat lanjut. Dengan menerapkan {i>progressive enhancement<i} dalam pengembangan aplikasi saya, Saya memastikan bahwa semua orang mendapatkan pengalaman dasar yang baik dan solid, tetapi pengguna browser yang mendukung lebih banyak API platform Web mendapatkan pengalaman yang lebih baik. Saya menantikan untuk melihat apa yang dapat Anda lakukan dengan progressive enhancement di aplikasi Anda.

Ucapan terima kasih

Saya berterima kasih kepada Christian Liebel dan Hemanth HM yang keduanya telah berkontribusi untuk Salam Fugu. Artikel ini ditinjau oleh Joe Medley dan Kayce Basques. Jake Archibald membantu saya mencari tahu situasinya dengan import() dinamis dalam konteks pekerja layanan.