SPA'ların ötesinde: PWA'nız için alternatif mimariler

Mimari hakkında konuşalım mı?

Önemli ancak yanlış anlaşılmaya müsait bir konuyu ele alacağım: Web uygulamanız için kullandığınız mimari ve özellikle de artan web uygulaması oluştururken mimari kararlarınızın nasıl devreye girdiği.

"Mimari" ifadesi belirsiz görünebilir ve bunun neden önemli olduğu hemen anlaşılmayabilir. Mimariyi düşünmenin bir yolu da kendinize şu soruları sormaktır: Bir kullanıcı sitemdeki bir sayfayı ziyaret ettiğinde hangi HTML yüklenir? Başka bir sayfayı ziyaret ettiklerinde hangi içerikler yükleniyor?

Bu soruların yanıtları her zaman net değildir ve ilerleyici web uygulamaları hakkında düşünmeye başladığınızda daha da karmaşık hale gelebilir. Bu nedenle, etkili olduğunu düşündüğüm olası bir mimariyi sizinle paylaşmak istiyorum. Bu makalede, progresif web uygulaması oluştururken aldığım kararları "yaklaşımım" olarak etiketleyeceğim.

Kendi PWA'nızı oluştururken yaklaşımımı kullanabilirsiniz ancak her zaman başka geçerli alternatifler de vardır. Tüm parçaların nasıl bir araya geldiğini görmenin size ilham vereceğini ve bu şablonu ihtiyaçlarınıza göre özelleştirebileceğinizi umuyorum.

Stack Overflow PWA

Bu makaleye eşlik etmesi için bir Stack Overflow PWA oluşturdum. Stack Overflow'da çok zaman geçirip katkıda bulunuyorum. Belirli bir konuyla ilgili sık sorulan sorulara göz atmayı kolaylaştıracak bir web uygulaması oluşturmak istedim. Herkese açık Stack Exchange API'nin üzerine kurulmuştur. Açık kaynaklıdır ve GitHub projesini ziyaret ederek daha fazla bilgi edinebilirsiniz.

Çok Sayfalı Uygulamalar (MPA'lar)

Ayrıntılara girmeden önce bazı terimleri tanımlayalım ve temel teknolojinin parçalarını açıklayalım. İlk olarak, "Çok Sayfalı Uygulamalar " veya"MPA'lar" olarak adlandırdığım konuyu ele alacağım.

MPA, web'in başlangıcından beri kullanılan geleneksel mimarinin şık bir adıdır. Kullanıcı yeni bir URL'ye her gittiğinde tarayıcı, o sayfaya özel HTML'yi aşamalı olarak oluşturur. Sayfanın durumu veya gezinmeler arasındaki içerik korunmaya çalışılmıyor. Yeni bir sayfayı her ziyaret ettiğinizde yeni bir başlangıç yaparsınız.

Bu, web uygulamaları oluşturmak için kullanılan tek sayfalık uygulama (SPA) modelinin aksine bir durumdur. SPA modelinde, kullanıcı yeni bir bölümü ziyaret ettiğinde tarayıcı, mevcut sayfayı güncellemek için JavaScript kodunu çalıştırır. Hem tek sayfalık uygulamalar hem de çok sayfalık uygulamalar eşit derecede geçerli modellerdir ancak bu yayında, çok sayfalık bir uygulama bağlamında PWA kavramlarını incelemek istedim.

Güvenilir şekilde hızlı

Benden (ve sayısız başka kişiden) "progresif web uygulaması" veya PWA ifadesini duyduğunuzu biliyoruz. Arka plan materyallerinin bir kısmını bu sitenin başka bir bölümünde görmüş olabilirsiniz.

PWA'yı, birinci sınıf kullanıcı deneyimi sunan ve kullanıcının ana ekranında yer almayı gerçekten hak eden bir web uygulaması olarak düşünebilirsiniz. Hızlı, Entegre, Güvenilir ve İlgi çekici anlamına gelen "FIRE" kısaltması, PWA oluştururken dikkate alınması gereken tüm özellikleri özetler.

Bu makalede, bu özelliklerin bir alt kümesi olan Hızlı ve Güvenilir özelliklerine odaklanacağım.

Hızlı: "Hızlı" kelimesi farklı bağlamlarda farklı anlamlara gelse de ben ağdan mümkün olduğunca az şey yüklemenin hızla ilgili avantajlarından bahsedeceğim.

Güvenilir: Ancak ham hız yeterli değildir. Web uygulamanızın PWA gibi görünmesi için güvenilir olması gerekir. Ağın durumundan bağımsız olarak, özelleştirilmiş bir hata sayfası olsa bile her zaman bir şeyler yükleyecek kadar dayanıklı olmalıdır.

Güvenilir şekilde hızlı: Son olarak, PWA tanımını biraz yeniden ifade edeceğim ve güvenilir şekilde hızlı bir şey oluşturmanın ne anlama geldiğine bakacağım. Yalnızca düşük gecikmeli bir ağdayken hızlı ve güvenilir olmak yeterli değildir. Güvenilir bir şekilde hızlı olmak, web uygulamanızın hızının temel ağ koşullarından bağımsız olarak tutarlı olması anlamına gelir.

Etkinleştirici Teknolojiler: Service Worker'lar + Cache Storage API

PWA'lar hız ve esneklik konusunda yüksek standartlar getirir. Neyse ki web platformu, bu tür bir performansı gerçeğe dönüştürmek için bazı yapı taşları sunuyor. Service worker'lar ve Cache Storage API'yi kastediyorum.

Cache Storage API aracılığıyla gelen istekleri dinleyen, bazılarını ağa ileten ve yanıtın bir kopyasını gelecekte kullanılmak üzere depolayan bir hizmet çalışanı oluşturabilirsiniz.

Bir ağ yanıtının kopyasını kaydetmek için Cache Storage API'yi kullanan bir hizmet çalışanı.

Web uygulaması aynı isteği bir sonraki sefer yaptığında hizmet çalışanı, önbelleklerini kontrol edip daha önce önbelleğe alınmış yanıtı döndürebilir.

Yanıt vermek için Cache Storage API'yi kullanan ve ağı atlayan bir service worker.

Mümkün olduğunda ağdan kaçınmak, güvenilir şekilde hızlı performans sunmanın önemli bir parçasıdır.

"İzomorfik" JavaScript

Bahsetmek istediğim bir diğer kavram da bazen "izomorfik" veya "evrensel" JavaScript olarak adlandırılan kavramdır. Basitçe ifade etmek gerekirse, aynı JavaScript kodunun farklı çalışma zamanı ortamları arasında paylaşılabileceği fikridir. PWA'mı oluştururken arka uç sunucum ile hizmet çalışanı arasında JavaScript kodu paylaşmak istedim.

Bu şekilde kod paylaşmak için birçok geçerli yaklaşım vardır ancak benim yaklaşımım, kesin kaynak kodu olarak ES modüllerini kullanmaktı. Ardından, Babel ve Rollup'ı birlikte kullanarak bu modülleri sunucu ve hizmet çalışanı için derleyip paketledim. Projemde, .mjs dosya uzantılı dosyalar bir ES modülünde bulunan kodlardır.

Sunucu

Bu kavramları ve terminolojiyi göz önünde bulundurarak Stack Overflow PWA'mı nasıl oluşturduğuma geçelim. Öncelikle arka uç sunucumuzu ele alıp bunun genel mimariye nasıl uyduğunu açıklayacağım.

Statik barındırma ile birlikte dinamik bir arka uç kombinasyonu arıyordum ve Firebase platformunu kullanmayı planlıyordum.

Firebase Cloud Functions, gelen bir istek olduğunda otomatik olarak Node tabanlı bir ortam oluşturur ve zaten aşina olduğum popüler Express HTTP çerçevesi ile entegre olur. Ayrıca, sitemdeki tüm statik kaynaklar için kullanıma hazır barındırma hizmeti de sunuyor. Sunucunun istekleri nasıl işlediğine göz atalım.

Bir tarayıcı sunucumuza karşı gezinme isteğinde bulunduğunda aşağıdaki akıştan geçer:

Sunucu tarafında gezinme yanıtı oluşturmaya genel bakış.

Sunucu, isteği URL'ye göre yönlendirir ve tam bir HTML belgesi oluşturmak için şablon oluşturma mantığını kullanır. Stack Exchange API'den alınan verilerin yanı sıra sunucunun yerel olarak depoladığı kısmi HTML parçalarını birlikte kullanıyorum. Hizmet çalışanımız nasıl yanıt vereceğini öğrendikten sonra HTML'yi web uygulamamıza geri aktarmaya başlayabilir.

Bu resimde daha ayrıntılı olarak incelenmesi gereken iki bölüm vardır: yönlendirme ve şablon oluşturma.

Yönlendirme

Yönlendirme konusunda, Express çerçevesinin yerel yönlendirme söz dizimini kullanmayı tercih ettim. Basit URL önekleriyle ve yolun bir parçası olarak parametreler içeren URL'lerle eşleşecek kadar esnektir. Burada, eşleştirilecek temel Express kalıbı ile rota adları arasında eşleme oluşturuyorum.

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

export default routes;

Daha sonra bu eşlemeye doğrudan sunucunun kodundan başvurabilirim. Belirli bir Express kalıbı eşleştiğinde uygun işleyici, eşleşen rotaya özgü şablon oluşturma mantığıyla yanıt verir.

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

Sunucu tarafı şablon oluşturma

Peki bu şablon oluşturma mantığı nasıl görünüyor? Ben de kısmi HTML parçalarını sırayla, birbirinin ardından birleştiren bir yaklaşım kullandım. Bu model, akış için uygundur.

Sunucu, bazı ilk HTML standartlarını hemen geri gönderir ve tarayıcı bu kısmi sayfayı hemen oluşturabilir. Sunucu, veri kaynaklarının geri kalanını bir araya getirirken belge tamamlanana kadar bunları tarayıcıya aktarır.

Ne demek istediğimi anlamak için rotalarımızdan birinin Express koduna göz atın:

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();
});

response nesnesinin write() yöntemini kullanarak ve yerel olarak depolanan kısmi şablonlara referans vererek, herhangi bir harici veri kaynağını engellemeden yanıt akışını hemen başlatabiliyorum. Tarayıcı bu ilk HTML'yi alır ve hemen anlamlı bir arayüz ile yükleme mesajı oluşturur.

Sayfamızın sonraki bölümünde Stack Exchange API'den alınan veriler kullanılır. Bu verilerin alınması için sunucumuzun bir ağ isteği göndermesi gerekir. Web uygulaması, yanıt alıp işleyene kadar başka bir şey oluşturamaz ancak en azından kullanıcılar beklerken boş bir ekrana bakmaz.

Web uygulaması, Stack Exchange API'den yanıtı aldıktan sonra API'den gelen verileri karşılık gelen HTML'ye çevirmek için özel bir şablon oluşturma işlevini çağırır.

Şablon dili

Şablon oluşturma, şaşırtıcı derecede tartışmalı bir konu olabilir. Benim kullandığım yöntem, birçok yaklaşımdan yalnızca biridir. Özellikle mevcut bir şablon oluşturma çerçevesiyle eski bağlantılarınız varsa kendi çözümünüzü kullanmak isteyebilirsiniz.

Kullanım alanım için mantıklı olan, bazı mantıkların yardımcı işlevlere ayrılmasıyla birlikte yalnızca JavaScript'in şablon değişmezlerine güvenmekti. MPA oluşturmanın güzel yanlarından biri, durum güncellemelerini takip etmeniz ve HTML'nizi yeniden oluşturmanız gerekmemesidir. Bu nedenle, statik HTML üreten temel bir yaklaşım benim için işe yaradı.

Bu nedenle, web uygulamamın dizinindeki dinamik HTML bölümünü nasıl şablonladığıma dair bir örnek veriyorum. Rotalarımda olduğu gibi, şablon oluşturma mantığı da hem sunucuya hem de hizmet çalışanına aktarılabilen bir ES modülünde depolanır.

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;
}

Bu şablon işlevleri saf JavaScript'tir ve mantığı gerektiğinde daha küçük yardımcı işlevlere ayırmak faydalıdır. Burada, API yanıtında döndürülen öğelerin her birini, uygun tüm özelliklerin ayarlandığı standart bir HTML öğesi oluşturan bir işleve iletiyorum.

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

Her bağlantıya eklediğim data-cache-url veri özelliği, özellikle dikkat çekicidir. Bu özellik, ilgili soruyu görüntülemek için ihtiyaç duyduğum Stack Exchange API URL'sine ayarlanmıştır. Bunu unutmayın. Daha sonra tekrar gözden geçireceğim.

Rota işleyicime geri dönecek olursak şablon oluşturma işlemi tamamlandıktan sonra sayfamın HTML'sinin son bölümünü tarayıcıya aktarıp akışı sonlandırıyorum. Bu, tarayıcıya aşamalı oluşturma işleminin tamamlandığını bildiren ipucudur.

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();
});

Sunucu kurulumumla ilgili kısa bir tur yaptık. Web uygulamamı ilk kez ziyaret eden kullanıcılar her zaman sunucudan yanıt alıyor ancak bir ziyaretçi web uygulamama geri döndüğünde service worker'ım yanıt vermeye başlıyor. Hemen başlayalım.

Hizmet çalışanı

Service worker&#39;da gezinme yanıtı oluşturmaya genel bakış.

Bu şema size tanıdık gelecektir. Daha önce ele aldığım parçaların çoğu burada biraz farklı bir düzenlemeyle yer alıyor. Hizmet çalışanını dikkate alarak istek akışını adım adım inceleyelim.

Service worker'ımız, belirli bir URL için gelen gezinme isteğini işler ve sunucumun yaptığı gibi, nasıl yanıt vereceğini anlamak için yönlendirme ve şablon oluşturma mantığının bir kombinasyonunu kullanır.

Yaklaşım eskisiyle aynıdır ancak fetch() ve Cache Storage API gibi farklı düşük düzeyli temel öğeler kullanılır. Bu veri kaynaklarını, hizmet çalışanının web uygulamasına geri ilettiği HTML yanıtını oluşturmak için kullanıyorum.

Workbox

Düşük düzeydeki temel öğelerle sıfırdan başlamak yerine, hizmet çalışanımı Workbox adlı bir dizi üst düzey kitaplığın üzerine kuracağım. Tüm hizmet çalışanlarının önbelleğe alma, yönlendirme ve yanıt oluşturma mantığı için sağlam bir temel sağlar.

Yönlendirme

Sunucu tarafı kodumda olduğu gibi, hizmet çalışanımın da gelen bir isteği uygun yanıt mantığıyla nasıl eşleştireceğini bilmesi gerekir.

Yaklaşımım, her Express rotasını çevirerek karşılık gelen bir normal ifadeye dönüştürmek ve regexparam adlı faydalı bir kitaplıktan yararlanmaktı. Bu çeviri yapıldıktan sonra Workbox'ın normal ifade yönlendirmesi için yerleşik desteğinden yararlanabilirim.

Normal ifadeleri içeren modülü içe aktardıktan sonra her normal ifadeyi Workbox'ın yönlendiricisine kaydediyorum. Her rotada yanıt oluşturmak için özel şablon mantığı sağlayabilirim. Hizmet çalışanındaki şablon oluşturma, arka uç sunucumdakinden biraz daha karmaşık olsa da Workbox, ağır işlerin çoğunda yardımcı oluyor.

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

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

Statik öğe önbelleğe alma

Şablon oluşturma sürecinin önemli bir parçası, kısmi HTML şablonlarımın Cache Storage API aracılığıyla yerel olarak kullanılabilir olmasını ve web uygulamasında değişiklikleri dağıttığımda güncel kalmasını sağlamaktır. Önbellek bakımı manuel olarak yapıldığında hataya açık olabilir. Bu nedenle, derleme sürecimin bir parçası olarak önbelleğe alma işlemini yönetmek için Workbox'ı kullanıyorum.

Workbox'a, önbelleğe alınacak URL'leri yapılandırma dosyası kullanarak bildiriyorum. Bu dosya, eşleştirilecek bir dizi kalıpla birlikte tüm yerel öğelerimi içeren dizine işaret ediyor. Bu dosya, siteyi her yeniden oluşturduğumda çalıştırılan Workbox'ın CLI'sı tarafından otomatik olarak okunur.

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

Workbox, her dosyanın içeriğinin anlık görüntüsünü alır ve bu URL ile düzeltme listesini nihai hizmet çalışanı dosyama otomatik olarak yerleştirir. Workbox artık önbelleğe alınan dosyaların her zaman kullanılabilir ve güncel olmasını sağlamak için gereken her şeye sahip. Sonuç, aşağıdakine benzer bir şey içeren bir service-worker.js dosyasıdır:

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

Daha karmaşık bir derleme süreci kullananlar için Workbox, komut satırı arayüzünün yanı sıra hem webpack eklentisine hem de genel bir düğüm modülüne sahiptir.

Canlı Yayın

Ardından, hizmet çalışanının önbelleğe alınmış bu kısmi HTML'yi web uygulamasına hemen geri aktarmasını istiyorum. Bu, "güvenilir şekilde hızlı" olmanın önemli bir parçasıdır. Ekranda her zaman anlamlı bir şey gösterilir. Neyse ki hizmet çalışanımızda Streams API'yi kullanarak bu mümkün hale geliyor.

Streams API'yi daha önce duymuş olabilirsiniz. Meslektaşım Jake Archibald, yıllardır bu özelliği övüyor. 2016'nın web yayınlarının yılı olacağı yönünde kesin bir tahminde bulundu. Streams API, iki yıl önceki kadar harika olsa da önemli bir fark var.

O zamanlar yalnızca Chrome, Streams'i destekliyordu ancak Streams API artık daha geniş bir destek kapsamına sahip. Genel olarak olumlu bir tablo söz konusu ve uygun yedek kodla, hizmet çalışanınzda akışları kullanmanızı engelleyen hiçbir şey yok.

Aslında sizi durduran tek şey, Streams API'nin nasıl çalıştığını anlamak olabilir. Çok güçlü bir temel işlevler grubu sunar ve bu işlevleri kullanmaktan çekinmeyen geliştiriciler, aşağıdakiler gibi karmaşık veri akışları oluşturabilir:

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);
        }
      });
  },
});

Ancak bu kodun tüm etkilerini anlamak herkes için uygun olmayabilir. Bu mantığı ayrıştırmak yerine, hizmet çalışanı akışına yaklaşımım hakkında konuşalım.

workbox-streams adlı yepyeni ve üst düzey bir sarmalayıcı kullanıyorum. Bu sayede, hem önbelleklerden hem de ağdan gelebilecek çalışma zamanı verilerinden oluşan bir akış kaynağı karışımında iletebilirim. Workbox, bağımsız kaynakları koordine etme ve bunları tek bir akış yanıtında birleştirme işini yapar.

Ayrıca Workbox, Streams API'nin desteklenip desteklenmediğini otomatik olarak algılar ve desteklenmediği durumlarda eşdeğer bir akış içermeyen yanıt oluşturur. Bu sayede, akışlar% 100 tarayıcı desteğine yaklaştıkça yedek yazma konusunda endişelenmenize gerek kalmaz.

Çalışma zamanı önbelleğe alma

Stack Exchange API'sinden gelen çalışma zamanı verilerinin service worker tarafından nasıl işlendiğine göz atalım. Web uygulamasının depolama alanının sınırsız büyümemesi için, Workbox'ın eski sürümü yeniden doğrularken önbelleğe alma stratejisi için yerleşik desteğini ve geçerlilik süresini kullanıyorum.

Yayın yanıtını oluşturacak farklı kaynakları işlemek için Workbox'ta iki strateji oluşturdum. Workbox, birkaç işlev çağrısı ve yapılandırmayla, aksi takdirde yüzlerce satır el yazısı kod gerektirecek işlemleri yapmamıza olanak tanıyor.

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})],
});

İlk strateji, kısmi HTML şablonlarımız gibi önbelleğe alınmış verileri okur.

Diğer strateji, 50 girişe ulaştığımızda en son kullanılmamış önbellek geçerlilik süresiyle birlikte stale-while-revalidate önbelleğe alma mantığını uygular.

Bu stratejileri uyguladıktan sonra, Workbox'a bunları nasıl kullanacağını söylemek kaldı. Kaynak dizisini işlev olarak iletiyorum ve bu işlevlerin her biri hemen yürütülüyor. Workbox, her kaynaktan aldığı sonucu web uygulamasına sırayla aktarır ve yalnızca dizideki bir sonraki işlev henüz tamamlanmamışsa gecikme yaşanır.

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'}),
]);

İlk iki kaynak, doğrudan Cache Storage API'den okunan önceden önbelleğe alınmış kısmi şablonlardır. Bu nedenle, her zaman anında kullanılabilirler. Bu, hizmet çalışanı uygulamamızın, sunucu tarafı kodum gibi isteklere güvenilir bir şekilde hızlı yanıt vermesini sağlar.

Bir sonraki kaynak işlevimiz, Stack Exchange API'den verileri getirir ve yanıtı web uygulamasının beklediği HTML'ye dönüştürür.

Yeniden doğrulama sırasında eski stratejisi, bu API çağrısı için daha önce önbelleğe alınmış bir yanıtım varsa önbellek girişini bir sonraki istek için "arka planda" güncellerken yanıtı hemen sayfaya aktarabileceğimi ifade eder.

Son olarak, yanıtı tamamlamak için altbilgimin önbelleğe alınmış bir kopyasını yayınlıyor ve son HTML etiketlerini kapatıyorum.

Paylaşım kodu sayesinde her şey senkronize kalır

Service worker kodunun belirli kısımlarının tanıdık olduğunu fark edeceksiniz. Hizmet çalışanım tarafından kullanılan kısmi HTML ve şablon oluşturma mantığı, sunucu tarafı işleyicimin kullandığıyla aynı. Bu kod paylaşımı, kullanıcıların web uygulamamı ilk kez ziyaret etmelerinden veya hizmet çalışanı tarafından oluşturulan bir sayfaya geri dönmelerinden bağımsız olarak tutarlı bir deneyim elde etmelerini sağlar. İzomorfik JavaScript'in güzelliği de budur.

Dinamik, progresif geliştirmeler

PWA'm için hem sunucuyu hem de hizmet çalışanını inceledim ancak ele alınması gereken son bir mantık parçası var: Tam olarak yayınlandıktan sonra sayfalarımın her birinde çalışan küçük bir JavaScript miktarı var.

Bu kod, kullanıcı deneyimini kademeli olarak iyileştirir ancak zorunlu değildir. Çalıştırılmasa bile web uygulaması çalışmaya devam eder.

Sayfa meta verisi

Uygulamam, bir sayfanın meta verilerini API yanıtına göre güncellemek için istemci tarafında JavaScript kullanıyor. Her sayfa için aynı önbelleğe alınmış HTML'nin ilk bölümünü kullandığımdan web uygulaması, dokümanımın başlığında genel etiketlerle sonuçlanıyor. Ancak şablon oluşturma ve istemci tarafı kodum arasındaki koordinasyon sayesinde, pencerenin başlığını sayfaya özel meta verileri kullanarak güncelleyebilirim.

Şablon oluşturma kodu kapsamında, uygun şekilde çıkışı yapılmış dizeyi içeren bir komut dosyası etiketi eklemeyi tercih ediyorum.

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

Ardından, sayfam yüklendikten sonra bu dizeyi okuyup doküman başlığını güncelliyorum.

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

Kendi web uygulamanızda güncellemek istediğiniz başka sayfaya özel meta veriler varsa aynı yaklaşımı izleyebilirsiniz.

Çevrimdışı kullanıcı deneyimi

Eklediğim diğer aşamalı geliştirme ise çevrimdışı özelliklerimize dikkat çekmek için kullanılıyor. Güvenilir bir PWA oluşturdum ve kullanıcıların internete bağlı olmadıklarında daha önce ziyaret ettikleri sayfaları yükleyebileceklerini bilmelerini istiyorum.

Öncelikle, daha önce önbelleğe alınan tüm API isteklerinin listesini almak için Cache Storage API'yi kullanıyorum ve bunu URL listesine çeviriyorum.

Bahsettiğim özel veri özelliklerini hatırlıyor musunuz? Bunların her biri, bir soruyu göstermek için gereken API isteğinin URL'sini içeriyordu. Bu veri özelliklerini, önbelleğe alınmış URL'lerin listesiyle çapraz referanslayabilir ve eşleşmeyen tüm soru bağlantılarının bir dizisini oluşturabilirim.

Tarayıcı çevrimdışı duruma girdiğinde, önbelleğe alınmamış bağlantıların listesinde döngü oluşturur ve çalışmayacak olanları karartır. Bu işlemin, kullanıcılara söz konusu sayfalardan ne beklemeleri gerektiği konusunda yalnızca görsel bir ipucu verdiğini, bağlantıları devre dışı bırakmadığını veya kullanıcının gezinmesini engellemediğini unutmayın.

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);

Sık karşılaşılan sorunlar

Çok sayfalı PWA oluşturma yaklaşımımı anlatan bir turu tamamladım. Kendi yaklaşımınızı oluştururken dikkate almanız gereken birçok faktör vardır ve benim yaptığım seçimlerden farklı seçimler yapabilirsiniz. Bu esneklik, web için geliştirme yapmanın en güzel yanlarından biridir.

Kendi mimari kararlarınızı verirken karşılaşabileceğiniz birkaç yaygın tuzak vardır ve sizi bu tuzaklardan kurtarmak istiyorum.

Tam HTML'yi önbelleğe almayın

Önbelleğinizde eksiksiz HTML belgeleri depolamamanızı öneririm. Öncelikle, bu bir alan israfıdır. Web uygulamanızın her sayfasında aynı temel HTML yapısı kullanılıyorsa aynı işaretlemenin kopyalarını tekrar tekrar depolarsınız.

Daha da önemlisi, sitenizin paylaşılan HTML yapısında bir değişiklik dağıtırsanız daha önce önbelleğe alınmış sayfaların her biri eski düzeninizle kalır. Geri gelen bir ziyaretçinin eski ve yeni sayfaların karışımını görmesinin ne kadar sinir bozucu olabileceğini düşünün.

Sunucu / service worker sapması

Kaçınılması gereken diğer bir hata da sunucunuzun ve hizmet çalışanınızın senkronizasyonunun bozulmasıdır. Yaklaşımım, aynı kodun her iki yerde de çalıştırılması için izomorfik JavaScript kullanmaktı. Mevcut sunucu mimarinize bağlı olarak bu her zaman mümkün olmayabilir.

Hangi mimari kararları alırsanız alın, sunucunuzda ve hizmet çalışanınızda eşdeğer yönlendirme ve şablon oluşturma kodunu çalıştırmak için bir stratejiniz olmalıdır.

En kötü senaryolar

Tutarsız düzen / tasarım

Bu tuzakları göz ardı ettiğinizde ne olur? Çeşitli hatalar oluşabilir ancak en kötü senaryo, geri gelen bir kullanıcının çok eski bir düzene sahip, önbelleğe alınmış bir sayfayı ziyaret etmesidir. Bu sayfada, güncel olmayan başlık metni olabilir veya artık geçerli olmayan CSS sınıf adları kullanılabilir.

En kötü senaryo: Yönlendirme bozuldu

Alternatif olarak, bir kullanıcı sunucunuz tarafından işlenen ancak hizmet çalışanı tarafından işlenmeyen bir URL ile karşılaşabilir. Zombi düzenler ve çıkmaz sokaklarla dolu bir site, güvenilir bir PWA değildir.

Başarı için ipuçları

Ancak bu süreçte yalnız değilsiniz. Aşağıdaki ipuçları, bu tuzaklardan kaçınmanıza yardımcı olabilir:

Çok dilli uygulamaları olan şablon oluşturma ve yönlendirme kitaplıklarını kullanma

JavaScript uygulamaları olan şablon oluşturma ve yönlendirme kitaplıklarını kullanmayı deneyin. Her geliştiricinin mevcut web sunucusundan ve şablon oluşturma dilinden geçiş yapma lüksü olmadığını biliyorum.

Ancak popüler şablon oluşturma ve yönlendirme çerçevelerinin birçoğu birden fazla dilde uygulanabilir. Hem JavaScript ile hem de mevcut sunucunuzun diliyle çalışan bir hizmet çalışanı bulursanız hizmet çalışanı ve sunucunuzu senkronize tutmaya bir adım daha yaklaşırsınız.

İç içe yerleştirilmiş şablonlar yerine sıralı şablonları tercih edin

Ardından, sırayla yayınlanabilecek bir dizi sıralı şablon kullanmanızı öneririm. HTML'nizin ilk bölümünü olabildiğince hızlı bir şekilde yayınlayabildiğiniz sürece, sayfanızın sonraki bölümlerinde daha karmaşık şablon oluşturma mantığı kullanabilirsiniz.

Hizmet çalışanı içinde hem statik hem de dinamik içeriği önbelleğe alma

En iyi performans için sitenizin tüm kritik statik kaynaklarını önbelleğe almanız gerekir. Ayrıca, API istekleri gibi dinamik içerikleri işlemek için çalışma zamanı önbelleğe alma mantığı da ayarlamanız gerekir. Workbox'ı kullanmak, her şeyi sıfırdan uygulamak yerine iyi test edilmiş, üretime hazır stratejiler üzerine inşa edebileceğiniz anlamına gelir.

Yalnızca kesinlikle gerekli olduğunda ağda engelleme yapın

Bununla bağlantılı olarak, yalnızca önbellekten yanıt akışı yapmak mümkün olmadığında ağda engelleme yapmalısınız. Önbelleğe alınmış bir API yanıtını hemen göstermek, genellikle yeni verilerin beklenmesinden daha iyi bir kullanıcı deneyimi sunar.

Kaynaklar