Bulletin ekibinin, bir PWA geliştirirken hizmet çalışanları hakkında öğrendikleri.
Bu, Google Bulletin ekibinin dışa dönük bir PWA geliştirirken edindiği derslerle ilgili bir blog yayını serisinin ilk bölümüdür. Bu yayınlarda, karşılaştığımız bazı zorlukları, bu zorlukları aşmak için uyguladığımız yaklaşımları ve sık yapılan hatalardan kaçınmaya yönelik genel tavsiyeleri paylaşacağız. Bu, PWA'lara dair eksiksiz bir genel bakış değildir. Amacımız, ekibimizin deneyimlerinden edindiğimiz bilgileri paylaşmaktır.
Bu ilk yayında, önce biraz arka plan bilgisi vereceğiz ve ardından hizmet işçileri hakkında öğrendiklerimizi ayrıntılı olarak ele alacağız.
Arka plan
Bülten, 2017'nin ortalarından 2019'un ortalarına kadar aktif olarak geliştiriliyordu.
Neden PWA oluşturmayı tercih ettik?
Geliştirme sürecine değinmeden önce, bu proje için neden bir PWA oluşturmanın cazip bir seçenek olduğunu inceleyelim:
- Hızlı iterasyon yapma imkanı. 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 yaklaşık olarak eşit olarak bölünmüştür. PWA sayesinde her iki platformda da çalışacak tek bir web uygulaması oluşturabildik. 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üncellenebilir. Bu sayede, kullanımdaki güncel olmayan istemci sayısı azaltılabilir. Müşteriler için çok kısa bir taşıma süresiyle, arka uçta önemli değişiklikleri kullanıma sunabildik.
- İlk ve üçüncü taraf uygulamalarıyla kolayca entegre edilebilir. Bu tür entegrasyonlar uygulama için bir şarttı. PWA'larda bu genellikle bir URL'yi açmak anlamına geliyordu.
- 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. Servis çalışanları, gelişmiş önbelleğe alma stratejileri, çevrimdışı özellikler, arka plan senkronizasyonu gibi birçok avantaj sunar. Servis çalışanları bazı karmaşıklıklar da getirse de sağladıkları avantajların, ek karmaşıklıktan daha ağır bastığını tespit ettik.
Mümkünse oluşturun
Hizmet çalışanı komut dosyasını manuel olarak yazmamaya çalışın. Hizmet çalışanlarını manuel olarak yazmak, önbelleğe alınmış kaynakları manuel olarak yönetmeyi ve Workbox gibi çoğu hizmet çalışanı kitaplığında ortak olan mantığı yeniden yazmayı gerektirir.
Bununla birlikte, şirket içi teknoloji grubumuzun yapısı nedeniyle hizmet işleyicimizi oluşturmak ve yönetmek için bir kitaplık kullanamadık. Aşağıdaki bilgilerde bu durum bazen yansıtılabilir. Daha fazla bilgi için Oluşturulmayan hizmet çalışanlarıyla ilgili tuzaklar 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ımlar yapar. Örneğin, window
veya document
'ün kullanılabilir olduğu varsayılarak veya hizmet çalışanlarının kullanamadığı bir API'nin (XMLHttpRequest
, yerel depolama alanı vb.) kullanılması. Uygulamanız için ihtiyaç duyduğunuz kritik kitaplıkların hizmet çalışanıyla uyumlu olduğundan emin olun. Bu belirli PWA için kimlik doğrulama amacıyla gapi.js kullanmak istedik ancak hizmet çalışanlarını desteklemediği için bunu yapamadık. Kitaplık yazarları, hizmet çalışanı kullanım alanlarını desteklemek için mümkün olduğunda JavaScript bağlamıyla ilgili gereksiz varsayımları da azaltmalı veya kaldırmalıdır (ör. hizmet çalışanıyla uyumlu olmayan API'lerden ve global durumdan kaçınarak).
İlk kullanıma hazırlama sırasında IndexedDB'e erişmekten kaçının
Service Worker komut dosyanızı başlatırken IndexedDB'yi yazmayın. Aksi takdirde istenmeyen şu durumla karşılaşabilirsiniz:
- Kullanıcının N sürümüne sahip IndexedDB (IDB) web uygulaması var
- Yeni web uygulaması, N+1 IDB sürümüyle birlikte gönderilir
- Kullanıcı PWA'yı ziyaret eder ve bu da yeni hizmet çalışanının indirilmesini tetikler.
- 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 - 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
- Hizmet çalışanı takılıyor ve hiçbir zaman yüklenmiyor
Bizim durumumuzda, servis çalışanı yüklenirken önbellek geçersiz kılındı. Bu nedenle, servis çalışanı hiç yüklenmezse kullanıcılar güncellenmiş uygulamayı hiç almaz.
Esnek bir yapı oluşturun
Servis çalışanı komut dosyaları arka planda çalışsa da G/Ç işlemlerinin (ağ, IDB vb.) ortasındayken bile herhangi bir zamanda sonlandırılabilir. Uzun süren tüm işlemler herhangi bir noktada devam ettirilebilir olmalıdır.
Büyük dosyaları sunucuya yükleyip IDB'ye kaydeden bir senkronizasyon işlemi söz konusu olduğunda, kesintiye uğrayan kısmi yüklemeler için çözümümüz, dahili yükleme kitaplığımızın devam ettirilebilir sisteminden yararlanmaktı. Bu sistemde, devam ettirilebilir yükleme URL'si yüklemeden önce IDB'ye kaydedilir ve yükleme ilk kez tamamlanmazsa bu URL kullanılarak devam ettirilir. Ayrıca, uzun süren herhangi bir G/Ç işleminden önce, her kayıt için sürecin neresinde olduğumuzu belirtmek üzere durum IDB'ye kaydediliyordu.
Global duruma bağlı olmayın
Hizmet çalışanları farklı bir bağlamda bulunduğundan, varlığını bekleyebileceğiniz birçok simge mevcut değildir. Kodlarımızın çoğu hem window
bağlamında hem de hizmet çalışanı bağlamında (ör. günlük kaydı, işaretler, senkronizasyon) çalıştırıldı. Kod'un, yerel depolama veya çerezler gibi kullandığı hizmetler konusunda savunma amaçlı olması gerekir. Global nesneye tüm bağlamlarda çalışacak şekilde referans vermek için globalThis
kullanabilirsiniz. Ayrıca, komut dosyasının ne zaman sonlandırılacağı ve durumun ne zaman kaldırılacağı konusunda garanti olmadığından, global değişkenlerde depolanan verileri de dikkatli bir şekilde kullanın.
Yerel geliştirme
Hizmet işçilerinin önemli bir bileşeni, kaynakları yerel olarak önbelleğe almaktır. Ancak geliştirme sırasında bu durum, özellikle güncellemeler tembelce yapıldığında, istediğinizin tam zıttı olur. Sunucu çalışanını, ilgili sorunlarda hata ayıklama yapabilmek veya arka plan senkronizasyonu ya da bildirimler gibi diğer API'lerle çalışmak için yine de yüklemeniz gerekir. Chrome'da bunu Chrome Geliştirici Araçları üzerinden gerçekleştirebilirsiniz. Bunun için Ağ panelinde Önbelleği devre dışı bırak onay kutusunu etkinleştirin (Uygulama paneli > Hizmet çalışanları bölmesi) ve bellek önbelleğini de devre dışı bırakmak için Ağ panelinde Ağ için atla onay kutusunu etkinleştirin. Daha fazla tarayıcıyı kapsayabilmek için geliştirici derlemelerinde varsayılan olarak etkinleştirilen, hizmet işleyicimize önbelleğe almayı devre dışı bırakan bir işaret ekleyerek farklı bir çözüme yöneldik. Bu, geliştiricilerin her zaman en son değişiklikleri önbelleğe alma sorunu yaşamadan almalarını sağlar. Tarayıcının öğeleri önbelleğe almasını önlemek için Cache-Control: no-cache
başlığını da eklemeniz önemlidir.
Deniz Feneri
Lighthouse, PWA'lar için yararlı olan çeşitli hata ayıklama araçları sağlar. Bir siteyi tarayıp PWA'lar, performans, erişilebilirlik, SEO ve diğer en iyi uygulamaları kapsayan raporlar oluşturur. PWA olma ölçütlerinden birini ihlal ederseniz sizi uyarması için Lighthouse'u sürekli entegrasyonda çalıştırmanızı öneririz. Bu durum aslında bir kez yaşanmış, Service Worker'ın yükleme yapmadığı ve bizim üretime geçmeden önce bunun farkına varmadığımız olmuştu. Lighthouse'ı CI'mizin bir parçası haline getirmiş olsaydık bu sorunu önleyebilirdik.
Sürekli teslimi benimseyin
Hizmet çalışanları otomatik olarak güncellenebildiğinden kullanıcılar yükseltmeleri sınırlayamaz. Bu, kullanımdaki güncel olmayan istemci sayısını önemli ölçüde azaltır. Kullanıcı uygulamamızı açtığında hizmet çalışanı, yeni istemciyi yavaşça indirirken eski istemciyi sunuyordu. Yeni istemci indirildikten sonra kullanıcıdan yeni özelliklere erişmek için 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 güncellemeleri iOS/Android uygulamaları için yapabildikleri şekilde reddetmesi oldukça zordur.
Müşteriler için çok kısa bir taşıma süresiyle, önemli arka uç değişikliklerini kullanıma sunabildik. Genellikle, önemli değişiklikler yapmadan önce kullanıcıların yeni istemcileri güncellemesi için bir ay süre tanırız. Uygulama eskiyken yayınlanacağından, kullanıcı uygulamayı uzun süre açmamışsa eski istemcilerin kullanımda kalması mümkündü. iOS'te hizmet çalışanları birkaç hafta sonra çıkarılır. Bu nedenle bu durum yaşanmaz. Android'de bu sorun, eski içerikler yayınlanmadığında veya içeriklerin süresi birkaç hafta sonra manuel olarak sona erdirildiğinde azaltılabilir. Uygulamada, eski istemcilerden kaynaklanan sorunlarla hiç karşılaşmadık. Belirli bir ekibin bu konuda ne kadar katı olmak istediği, söz konusu kullanım alanına bağlıdır. Ancak PWA'lar, iOS/Android uygulamalarına kıyasla çok 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 durumumuzda, birinci taraf API isteklerinin kimliğini doğrulamak için jeton oluşturmak üzere çerez değerlerine erişmemiz gerekiyordu. Hizmet çalışanlarında document.cookies
gibi senkron 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 pencereli istemci olmadan arka planda çalışması da mümkündür. Bu sorunu gidermek için ön uç sunucumuzda, çerez değerini istemciye geri yansıtan 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ı
Statik önbelleğe alınmış dosyalarda değişiklik olursa hizmet çalışanı komut dosyasının değiştiğ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. Bu, istemcilerin sonraki tüm ziyaretler için doğrudan Cache Storage API önbelleğini kullanmasına olanak tanır. Servis çalışanları yalnızca tarayıcı, servis çalışanı komut dosyasının bir şekilde değiştiğini algıladığında yüklenir. Bu nedenle, önbelleğe alınmış bir dosya değiştiğinde servis çalışanı komut dosyasının da bir şekilde değiştiğ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
Hizmet çalışanı API'leri, genel nesneye etkinlik dinleyicileri ekleyerek çalışır. Örneğin:
self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));
Son olarak sonuç için iddiada bulunmadan önce etkinlik tetikleyicisini, etkinlik nesnesini taklit etmeniz, respondWith()
geri çağırma işlevini beklemeniz ve ardından promise'i beklemeniz gerektiğinden bu testin yapılması zor olabilir. 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)));
Bir hizmet çalışanı komut dosyasını birim test etmenin zorlukları nedeniyle, temel hizmet çalışanı komut dosyasını mümkün olduğunca basit tutarak uygulamanın büyük bir 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 bilgi edinmek için yazar profillerimizi ziyaret ederek bizimle nasıl iletişime geçeceğinizi öğrenin: