Google'da PWA oluşturma, 1. bölüm

Bulletin ekibinin PWA geliştirirken Service Worker'lar hakkında öğrendikleri.

Douglas Parker
Douglas Parker
Joel Riley
Joel Riley
Dikla Cohen
Dikla Cohen

Bu, Google Bulletin ekibinin şirket dışına yönelik bir PWA oluştururken aldığı derslerle ilgili blog yayını dizisinin ilkidir. Bu yayınlarda karşılaştığımız zorluklardan bazılarını, bunların üstesinden gelmek için benimsediğimiz yaklaşımları ve tuzaklardan kaçınmak için genel tavsiyelerimizi paylaşacağız. Bu, PWA'lara kapsamlı bir genel bakış anlamına gelmez. Amacımız, ekibimizin tecrübelerinden öğrendiklerimizi paylaşmaktır.

Bu ilk gönderimde, önce biraz arka plan bilgilerinden sonra hizmet çalışanları hakkında öğrendiğimiz tüm bilgileri ayrıntılı olarak ele alacağız.

Arka plan

Bulletin, 2017'nin ortalarından 2019'un ortalarına kadar aktif geliştirme aşamasındaydı.

Neden PWA oluşturmayı tercih ettik?

Geliştirme sürecine geçmeden önce, PWA oluşturmanın bu proje için neden cazip bir seçenek olduğuna bakalım:

  • Hızlı iterasyon yapabilme. Bu, Bulletin birden fazla pazarda pilot uygulama yapılacağı için özellikle değerlidir.
  • Tek kod tabanı. Kullanıcılarımız Android ve iOS arasında aşağı yukarı eşit bir şekilde dağılmıştı. PWA ise her iki platformda da çalışacak tek bir web uygulaması geliştirmek demekti. Bu da ekibin hızını ve etkisini artırdı.
  • Kullanıcı davranışından bağımsız olarak hızlı bir şekilde güncellenir. PWA'lar otomatik olarak güncellenip güncel olmayan istemci sayısını azaltır. İstemciler için çok kısa bir taşıma süresiyle arka uç değişikliklerini işlemeyi başardık.
  • Birinci ve üçüncü taraf uygulamalarıyla kolayca entegre edilir. Bu tür entegrasyonlar uygulama için bir gereklilikti. PWA ile genellikle yalnızca bir URL açılıyordu.
  • Uygulama yükleme zorluğu ortadan kaldırıldı.

Çerçevemiz

Bulletin için Polymer'i kullandık ancak iyi desteklenen tüm modern çerçeveler de işe yarayacaktır.

Service Worker'lar hakkında öğrendiklerimiz

Hizmet çalışanı olmadan PWA'nız olamaz. Service Worker'lar size gelişmiş önbelleğe alma stratejileri, çevrimdışı özellikler, arka planda senkronizasyon vb. gibi birçok güç sunar. Service Worker'lar karmaşıklık eklese de avantajların, eklenen karmaşıklıktan ağır bastığını gördük.

Mümkünse oluşturun

Service Worker komut dosyasını elle yazmaktan kaçının. Service Worker'ları manuel olarak yazmak, önbelleğe alınan kaynakların manuel olarak yönetilmesini ve Workbox gibi çoğu Service Worker kitaplığında ortak olan mantığın yeniden yazılmasını gerektirir.

Bununla birlikte, şirket içi teknoloji yığınımız nedeniyle Service Worker'ımızı oluşturmak ve yönetmek için bir kitaplık kullanamıyorduk. Aşağıdaki derslerimiz de bazen bunu yansıtabilir. Daha fazla bilgi edinmek için Oluşturulmayan hizmet çalışanları için tehlikeli noktalar başlıklı makaleyi inceleyin.

Tüm kitaplıklar hizmet çalışanıyla uyumlu değildir

Bazı JS kitaplıkları, bir hizmet çalışanı tarafından çalıştırıldığında beklendiği gibi çalışmayan varsayımlarda bulunur. Örneğin, window veya document varsa ya da hizmet çalışanlarına sunulmayan bir API (XMLHttpRequest, yerel depolama alanı vb.) kullanıldığında. Uygulamanız için ihtiyaç duyduğunuz kritik kitaplıkların hizmet çalışanlarıyla uyumlu olduğundan emin olun. Bu PWA ile kimlik doğrulama için gapi.js kullanmak istedik ancak hizmet çalışanlarını desteklemediğinden kullanamadık. Kitaplık yazarları, Service Worker ile uyumlu olmayan API'lerden kaçınmak ve global durumdan kaçınmak gibi, Service Worker kullanım alanlarını desteklemek için mümkün olduğunda JavaScript bağlamıyla ilgili gereksiz varsayımları azaltmalı veya kaldırmalıdır.

Başlatma sırasında IndexedDB'ye erişmekten kaçınma

Service Worker komut dosyanızı başlatırken IndexedDB'yi yazmayın. Aksi takdirde istenmeyen şu durumla karşılaşabilirsiniz:

  1. Kullanıcının IndexedDB (IDB) sürümü N olan web uygulaması var
  2. Yeni web uygulaması IDB sürümü N+1 ile aktarılır
  3. Kullanıcı, yeni hizmet çalışanının indirilmesini tetikleyen PWA'yı ziyaret eder
  4. Yeni hizmet çalışanı, install etkinlik işleyicisini kaydetmeden önce IDB'den okuma yaparak N'den N+1'e giden bir IDB yükseltme döngüsünü tetikler
  5. Kullanıcının sürümü N olan eski bir istemcisi olduğundan, etkin bağlantılar veritabanının eski sürümüne hâlâ açık olduğundan hizmet çalışanı yükseltme işlemi kilitlenir
  6. Hizmet çalışanı kilitleniyor ve hiçbir zaman yüklenmiyor

Bizim örneğimizde önbellek, Service Worker yüklemesi sırasında geçersiz kılınmıştır. Dolayısıyla, Service Worker hiç yüklenmezse kullanıcılar güncellenen uygulamayı hiçbir zaman alamamıştır.

Dayanıklı hale getirin

Service Worker komut dosyaları arka planda çalışsa da G/Ç işlemlerinin (ağ, IDB vb.) ortasında bile olsa herhangi bir zamanda sonlandırılabilir. Uzun süreli işlemler her noktada devam ettirilebilir.

Büyük dosyaları sunucuya yükleyip IDB'ye kaydeden bir senkronizasyon işlemi durumunda, kesintiye uğrayan kısmi yüklemeler için çözümümüz, dahili yükleme kitaplığımızın devam ettirilebilir sisteminden yararlanmak, devam ettirilebilir yükleme URL'sini yüklemeden önce IDB'ye kaydetmek ve yükleme ilk kez tamamlanmamışsa yüklemeyi devam ettirmek için bu URL'yi kullanmaktı. Ayrıca uzun süren herhangi bir G/Ç işleminden önce durum, her bir kayıt için işlemin hangi aşamasında olduğumuzu belirtmek üzere IDB'ye kaydediliyor.

Küresel duruma bağımlı olmama

Service Worker'lar farklı bir bağlamda yer aldığı için var olmasını beklediğiniz birçok sembol mevcut değildir. Kodumuzun büyük bir kısmı hem window bağlamında hem de Service Worker bağlamında (günlük kaydı, işaretler, senkronizasyon vb.) çalıştırılıyordu. Kodun kullandığı hizmetler (ör. yerel depolama alanı veya çerezler) konusunda savunma amaçlı olması gerekir. Global nesneye tüm bağlamlarda çalışacak şekilde başvurmak için globalThis kullanabilirsiniz. Ayrıca, genel değişkenlerde depolanan verileri ölçülü bir şekilde kullanın, çünkü komut dosyasının ne zaman sonlandırılacağı ve durumunun ne zaman çıkarılacağı konusunda bir garanti verilmez.

Yerel gelişim

Service Worker'ların önemli bir bileşeni, kaynakları yerel olarak önbelleğe almaktır. Ancak geliştirme sırasında bu, özellikle güncellemeler geç yapıldığında istediğinizin tam tersidir. Hataları ayıklayabilmek veya arka plan senkronizasyonu ya da bildirimler gibi diğer API'lerle çalışabilmek için sunucu çalışanının yüklü olmasını istersiniz. Chrome'da bu işlemi Chrome Geliştirici Araçları'ndan yapabilirsiniz. Bunun için panelindeki Önbelleği devre dışı bırak onay kutusunu işaretlemenin yanı sıra Ağı atla onay kutusunu (Uygulama paneli > Hizmet çalışanları bölmesi) etkinleştirin. Daha fazla tarayıcıyı kapsamak amacıyla farklı bir çözüm tercih ettik. Bunun için, geliştirici derlemelerinde varsayılan olarak etkin olan Service Worker'ımıza önbelleğe almayı devre dışı bırakacak bir işaret ekledik. Bu, geliştiricilerin her zaman en son değişiklikleri önbelleğe alma sorunu yaşamadan almalarını sağlar. Tarayıcının herhangi bir öğeyi önbelleğe almasını önlemek için Cache-Control: no-cache başlığının da eklenmesi önemlidir.

Deniz Feneri

Lighthouse, PWA'lar için faydalı birçok hata ayıklama aracı sunar. Bir siteyi tarayıp PWA'lar, performans, erişilebilirlik, SEO ve diğer en iyi uygulamaları kapsayan raporlar oluşturur. PWA kullanma kriterlerinden birini aşarsanız sizi uyarmak için Lighthouse'u sürekli entegrasyonda çalıştırmanızı öneririz. Bu aslında bir kez oldu. Service Worker yükleme yapmıyordu ve üretime geçmeden önce bunun farkında değildik. CI'mızın bir parçası olarak Lighthouse'un olması bunu önleyecektir.

Sürekli teslimi benimseyin

Service Worker'lar otomatik güncelleme yapabildiği için kullanıcılar yükseltmeleri sınırlandıramaz. Bu da vahşi ortamdaki güncel olmayan müşteri sayısını önemli ölçüde azaltır. Kullanıcı uygulamamızı açtığında hizmet çalışanı yeni istemciyi geç indirirken eski istemciye hizmet veriyordu. Yeni istemci indirildikten sonra, yeni özelliklere erişmek için kullanıcıdan sayfayı yenilemesi istenir. Kullanıcı bu isteği yoksaysa bile sayfayı bir sonraki yenilediğinde istemcinin yeni sürümünü alırdı. Sonuç olarak kullanıcıların, iOS/Android uygulamalarında olduğu gibi güncellemeleri reddetmesi oldukça zordur.

İstemciler için çok kısa bir taşıma süresiyle arka uç değişikliklerini işlemeyi başardık. Genellikle kullanıcılara zarar veren değişiklikler yapmadan önce yeni müşterilere güncelleme yapmaları için bir ay süre tanırız. Uygulama eskiyken uygulama sunulacağından, kullanıcı uzun süre uygulamayı açmasaydı eski istemcilerin aslında aktif kalması mümkündü. iOS'te, Service Worker'lar birkaç hafta sonra çıkarılır. Bu durum, böyle bir durum olmaz. Android'de bu sorun, eskiyken sunulmaması veya içeriğin birkaç hafta sonra manuel olarak süresinin dolması yoluyla azaltılabilir. Uygulamada, eski istemcilerden kaynaklanan sorunlarla hiç karşılaşmadık. Belirli bir ekibin burada olmak istediği ne kadar katı olduğu özel kullanım alanına bağlıdır ancak PWA'lar iOS/Android uygulamalarından önemli ölçüde daha fazla esneklik sağlar.

Service Worker'da çerez değerlerini alma

Bazen bir hizmet çalışanı bağlamında çerez değerlerine erişmek gerekir. Bizim örneğimizde, birinci taraf API isteklerinin kimliğini doğrulamak üzere bir jeton oluşturmak için çerez değerlerine erişmemiz gerekir. Service Worker'da document.cookies gibi eşzamanlı API'ler kullanılamaz. Hizmet çalışanından etkin (pencereli) istemcilere çerez değerlerini istemek için her zaman mesaj gönderebilirsiniz. Ancak Service Worker'ın, arka plan senkronizasyonu sırasında olduğu gibi herhangi bir aralıklı istemci olmadan arka planda çalışması da mümkündür. Bu sorunu çözmek için ön uç sunucumuzda, çerez değerini istemciye geri gönderen bir uç nokta oluşturduk. Hizmet çalışanı, bu uç noktaya ağ isteği gönderdi ve çerez değerlerini almak için yanıtı okudu.

Cookie Store API'nin kullanıma sunulmasıyla birlikte bu geçici çözüm, tarayıcı çerezlerine eşzamansız erişim sağladığı ve doğrudan hizmet çalışanı tarafından kullanılabildiği için bu API'yi destekleyen tarayıcılarda artık gerekli olmayacaktır.

Oluşturulmayan Service Worker'ların sorunları

Önbelleğe alınan statik dosya değiştiğinde hizmet çalışanı komut dosyasının değişeceğinden emin olun

Yaygın bir PWA kalıbı, bir hizmet çalışanının tüm statik uygulama dosyalarını install aşamasında yüklemesidir. Böylece istemciler, sonraki tüm ziyaretler için doğrudan Cache Storage API önbelleğine ulaşabilir. Service Worker'lar yalnızca tarayıcı, hizmet çalışanı komut dosyasının bir şekilde değiştiğini algıladığında yüklenir. Bu nedenle, önbelleğe alınan bir dosya değiştiğinde Service Worker komut dosyası dosyasının kendisinin de bir şekilde değiştirildiğinden emin olmamız gerekiyordu. Statik kaynak dosya kümesinin bir karmasını Service Worker komut dosyamıza yerleştirerek bu işlemi manuel olarak gerçekleştirdik. Böylece her sürüm, ayrı bir Service Worker JavaScript dosyası üretti. Workbox gibi hizmet çalışanı kitaplıkları bu işlemi sizin için otomatikleştirir.

Birim testi

Service Worker API'leri, global nesneye etkinlik işleyiciler ekleyerek çalışır. Örneğin:

self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));

Bu, test etmek zor olabilir. Çünkü etkinlik tetikleyicisi ve etkinlik nesnesiyle alay etmeniz, respondWith() geri çağırmasını ve ardından nihai olarak sonuç hakkında açıklama yapmadan önce taahhüdü beklemeniz gerekir. Bunu yapılandırmanın daha kolay bir yolu, tüm uygulamaları daha kolay test edilen başka bir dosyaya yetkilendirmektir.

import fetchHandler from './fetch_handler.js';
self.addEventListener('fetch', (evt) => evt.respondWith(fetchHandler(evt)));

Service Worker komut dosyasını birim test etmenin zorlukları nedeniyle, temel hizmet çalışanı komut dosyasını olabildiğince sade tuttuk ve uygulamanın büyük kısmını diğer modüllere ayırdık. Bu dosyalar yalnızca standart JS modülleri olduğundan, standart test kitaplıklarıyla daha kolay birim test edilebilirler.

2. ve 3. bölümler için bizi takip edin

Bu serinin 2. ve 3. bölümlerinde medya yönetimi ve iOS'a özgü sorunlardan bahsedeceğiz. Google'da PWA oluşturma hakkında daha fazla soru sormak isterseniz bizimle nasıl iletişime geçeceğinizi öğrenmek için yazar profillerimizi ziyaret edin: