PoP Aracılığıyla Bir WordPress Web Sitesine Kod Bölme Yetenekleri Ekleme

Yayınlanan: 2022-03-10
Kısa özet ↬ WP web sitelerinin Webpack aracılığıyla kod bölmeyi gerçekleştirmesinin kolay olmadığı bilinen bir gerçektir. Leonardo, işleri kendi eline alır ve PoP adlı açık kaynaklı bir çerçeve için kendi kod bölme sürümünü uygular.

Hız, günümüzde herhangi bir web sitesi için en önemli öncelikler arasındadır. Bir web sitesinin daha hızlı yüklenmesini sağlamanın bir yolu kod bölmektir: bir uygulamayı isteğe bağlı olarak yüklenebilecek parçalara bölmek - yalnızca gerekli JavaScript'i yüklemek ve başka bir şey yüklememek. JavaScript çerçevelerini temel alan web siteleri, popüler JavaScript paketleyici Webpack aracılığıyla kod bölmeyi hemen uygulayabilir. WordPress web siteleri için olsa da, o kadar kolay değil. İlk olarak, Webpack, WordPress ile çalışmak üzere kasıtlı olarak oluşturulmamıştır, bu nedenle onu kurmak oldukça geçici bir çözüm gerektirecektir; ikinci olarak, WordPress için yerel isteğe bağlı varlık yükleme yetenekleri sağlayan hiçbir araç mevcut görünmüyor.

WordPress için bu uygun bir çözüm eksikliği göz önüne alındığında, oluşturduğum WordPress web siteleri oluşturmak için açık kaynaklı bir çerçeve olan PoP için kendi kod bölme sürümümü uygulamaya karar verdim. PoP yüklü bir WordPress web sitesi, yerel olarak kod bölme özelliklerine sahip olacaktır, bu nedenle Webpack'e veya başka bir paketleyiciye bağımlı olması gerekmez. Bu makalede, çerçevenin mimarisinin yönlerine dayalı olarak hangi kararların alındığını açıklayarak nasıl yapıldığını size göstereceğim. Sonunda, kod bölmeli ve kod bölmesiz bir web sitesinin performansını ve harici bir paketleyici üzerinden özel bir uygulama kullanmanın yararlarını ve olumsuz yanlarını analiz edeceğim. Umarım sürüşün tadını çıkarırsın!

Stratejinin Tanımlanması

Kod bölme genel olarak şu iki adıma ayrılabilir:

  1. Her rota için hangi varlıkların yüklenmesi gerektiğini hesaplama,
  2. Bu varlıkları talep üzerine dinamik olarak yüklemek.

İlk adımın üstesinden gelmek için, uygulamamızdaki tüm varlıkları içeren bir varlık-bağımlılık haritası üretmemiz gerekecek. Varlıklar bu haritaya özyinelemeli olarak eklenmelidir - başka varlığa ihtiyaç duyulmayıncaya kadar bağımlılık bağımlılıkları da eklenmelidir. Daha sonra, rotanın giriş noktasından başlayarak (yani yürütmeye başladığı dosya veya kod parçası) son seviyeye kadar varlık-bağımlılık haritasını geçerek belirli bir rota için gereken tüm bağımlılıkları hesaplayabiliriz.

Atlamadan sonra daha fazlası! Aşağıdan okumaya devam edin ↓

İkinci adımın üstesinden gelmek için, sunucu tarafında istenen URL için hangi varlıkların gerekli olduğunu hesaplayabilir ve ardından uygulamanın bunları yüklemesi gereken yanıtta gerekli varlıkların listesini gönderebilir veya doğrudan HTTP/ 2 kaynakları yanıtın yanına itin.

Ancak bu çözümler optimal değildir. İlk durumda, uygulama, yanıt döndürüldükten sonra tüm varlıkları talep etmelidir, bu nedenle varlıkları getirmek için ek bir dizi gidiş-dönüş isteği olacaktır ve görünüm, tümü yüklenmeden önce oluşturulamadığından, sonuç olarak kullanıcının beklemesi gerekiyor (bu sorun, tüm varlıkların hizmet çalışanları aracılığıyla önbelleğe alınmasıyla kolaylaştırılır, bu nedenle bekleme süresi azalır, ancak yalnızca yanıt geri döndükten sonra gerçekleşen varlıkların ayrıştırılmasından kaçınamayız). İkinci durumda, aynı varlıkları tekrar tekrar zorlayabiliriz (çerezler aracılığıyla halihazırda hangi kaynakları yüklediğimizi belirtmek gibi bazı ekstra mantık eklemedikçe, ancak bu gerçekten istenmeyen karmaşıklık ekler ve yanıtın önbelleğe alınmasını engeller) ve biz varlıkları bir CDN'den sunamaz.

Bu nedenle, bu mantığın istemci tarafında ele alınmasına karar verdim. Her bir rota için hangi varlıkların gerekli olduğunun bir listesi, istemcideki uygulamaya sunulur, böylece, istenen URL için hangi varlıkların gerekli olduğunu zaten bilir. Bu, yukarıda belirtilen sorunları giderir:

  • Varlıklar, sunucunun yanıtını beklemek zorunda kalmadan hemen yüklenebilir. (Bunu hizmet çalışanları ile birleştirdiğimizde, yanıt geri geldiğinde tüm kaynakların yüklenip ayrıştırılacağından oldukça emin olabiliriz, bu nedenle ek bekleme süresi yoktur.)
  • Uygulama, hangi varlıkların zaten yüklendiğini bilir; bu nedenle, o rota için gereken tüm varlıkları talep etmeyecek, yalnızca henüz yüklenmemiş varlıkları isteyecektir.

Bu listeyi ön uca ulaştırmanın olumsuz yönü, web sitesinin boyutuna bağlı olarak (kaç tane rota sağladığı gibi) ağırlaşabilmesidir. Uygulamanın algılanan yükleme süresini artırmadan yüklemenin bir yolunu bulmamız gerekiyor. Bu konuda daha sonra.

Bu kararları verdikten sonra, uygulamada kod bölmeyi tasarlamaya ve ardından uygulamaya geçebiliriz. Anlamayı kolaylaştırmak için süreç aşağıdaki adımlara ayrılmıştır:

  1. Uygulamanın mimarisini anlamak,
  2. Varlık bağımlılıklarını eşleme,
  3. Tüm uygulama yollarının listelenmesi,
  4. Her rota için hangi varlıkların gerekli olduğunu tanımlayan bir liste oluşturmak,
  5. Dinamik olarak yüklenen varlıklar,
  6. Optimizasyonların uygulanması.

Hemen konuya girelim!

0. Uygulamanın Mimarisini Anlamak

Tüm varlıkların birbiriyle ilişkisini haritalandırmamız gerekecek. Bu amaca ulaşmak için en uygun çözümü tasarlamak için PoP mimarisinin özelliklerini gözden geçirelim.

PoP, WordPress'i saran, WordPress'i uygulamaya güç sağlayan CMS olarak kullanmamızı sağlayan, ancak dinamik web siteleri oluşturmak için istemci tarafında içerik oluşturmak için özel bir JavaScript çerçevesi sağlayan bir katmandır. Web sayfasının yapı bileşenlerini yeniden tanımlar: WordPress şu anda HTML üreten hiyerarşik şablonlar kavramına ( single.php , home.php ve archive.php gibi) dayalıyken, PoP “modüller, ” ya atomik bir işlevsellik ya da diğer modüllerin bir bileşimi. Bir PoP uygulaması oluşturmak, LEGO ile oynamaya benzer - modülleri üst üste istiflemek veya birbirini sarmak, sonuçta daha karmaşık bir yapı oluşturmak. Brad Frost'un atom tasarımının bir uygulaması olarak da düşünülebilir ve şöyle görünür:

Büyük önizleme

Modüller, bloklar, blokGroups, pageSections ve topLevels gibi üst düzey varlıklar olarak gruplandırılabilir. Bu varlıklar da sadece ek özelliklere ve sorumluluklara sahip modüllerdir ve her modülün tüm iç modüllerinin özelliklerini görebildiği ve değiştirebildiği katı bir şekilde yukarıdan aşağıya bir mimari izleyerek birbirlerini içerirler. Modüller arasındaki ilişki şu şekildedir:

  • 1 topLevel, N sayfa Bölüm içerir,
  • 1 sayfaBölüm, N blok veya blokGrubu içerir,
  • 1 blokGrubu, N blok veya blokGrubu içerir,
  • 1 blok N modül içerir,
  • 1 modül, sonsuz sayıda N modül içerir.

JavaScript Kodunu PoP'ta Yürütme

PoP, pageSection düzeyinden başlayarak, satırdaki tüm modülleri yineleyerek, her birini modülün önceden tanımlanmış Gidon şablonu aracılığıyla işleyerek ve son olarak, modülün karşılık gelen yeni oluşturulan öğelerini DOM'ye ekleyerek dinamik olarak HTML oluşturur. Bu yapıldıktan sonra, modül bazında önceden tanımlanmış JavaScript işlevlerini bunlar üzerinde yürütür.

PoP, JavaScript çerçevelerinden (React ve AngularJS gibi) farklıdır, çünkü uygulama akışı istemciden kaynaklanmaz, ancak yine de arka uçta, modülün yapılandırması içinde (bir PHP nesnesinde kodlanmıştır) yapılandırılır. WordPress eylem kancalarından etkilenen PoP, bir yayınla-abone ol modeli uygular:

  1. Her modül, bu kodu neyin çalıştıracağını veya nereden geleceğini önceden bilmeden, karşılık gelen yeni oluşturulan DOM öğelerinde hangi JavaScript işlevlerinin yürütülmesi gerektiğini tanımlar.
  2. JavaScript nesneleri, uyguladıkları JavaScript işlevlerini kaydetmelidir.
  3. Son olarak, çalışma zamanında PoP, hangi JavaScript nesnelerinin hangi JavaScript işlevlerini yürütmesi gerektiğini hesaplar ve bunları uygun şekilde çağırır.

Örneğin, ilgili PHP nesnesi aracılığıyla, bir takvim modülü, aşağıdaki gibi DOM öğelerinde yürütülmesi için calendar işlevinin gerekli olduğunu belirtir:

 class CalendarModule { function get_jsmethods() { $methods = parent::get_jsmethods(); $this->add_jsmethod($methods, 'calendar'); return $methods; } ... }

Ardından, bir JavaScript nesnesi - bu durumda popFullCalendar - calendar işlevini uyguladığını duyurur. Bu, popJSLibraryManager.register çağrılarak yapılır:

 window.popFullCalendar = { calendar : function(elements) { ... } }; popJSLibraryManager.register(popFullCalendar, ['calendar', ...]);

Son olarak, popJSLibraryManager , neyin hangi kodu çalıştırdığına ilişkin eşleştirmeyi yapar. JavaScript nesnelerinin hangi işlevleri uyguladıklarını kaydetmelerine izin verir ve abone olunan tüm JavaScript nesnelerinden belirli bir işlevi yürütmek için bir yöntem sağlar:

 window.popJSLibraryManager = { libraries: [], methods: {}, register : function(library, methods) { this.libraries.push(library); for (var i = 0; i < methods.length; i++) { var method = methods[i]; this.methods[method] = this.methods[method] || []; this.methods[method].push(library); } }, execute : function(method, elements) { var libraries = this.methods[method] || []; for (var i = 0; i < libraries.length; i++) { var library = libraries[i]; library[method](elements); } } }

calendar-293 kimliğine sahip DOM'a yeni bir takvim öğesi eklendikten sonra, PoP aşağıdaki işlevi yürütür:

 popJSLibraryManager.execute("calendar", document.getElementById("calendar-293"));

Giriş noktası

PoP için JavaScript kodunu çalıştırmanın giriş noktası, HTML çıktısının sonundaki şu satırdır:

 <script type="text/javascript">popManager.init();</script>

popManager.init() önce ön uç çerçevesini başlatır ve ardından yukarıda açıklandığı gibi işlenmiş tüm modüller tarafından tanımlanan JavaScript işlevlerini yürütür. Aşağıda bu işlevin çok basitleştirilmiş bir şekli verilmiştir (orijinal kod GitHub'dadır). popJSLibraryManager.execute('pageSectionInitialized', pageSection) ve popJSLibraryManager.execute('documentInitialized') , bu işlevleri uygulayan tüm JavaScript nesneleri ( pageSectionInitialized ve documentInitialized ) bunları yürütecektir.

 (function($){ window.popManager = { // The configuration for all the modules (including pageSections and blocks) in the application configuration : {...}, init : function() { var that = this; $.each(this.configuration, function(pageSectionId, configuration) { // Obtain the pageSection element in the DOM from the ID var pageSection = $('#'+pageSectionId); // Run all required JavaScript methods on it this.runJSMethods(pageSection, configuration); // Trigger an event marking the block as initialized popJSLibraryManager.execute('pageSectionInitialized', pageSection); }); // Trigger an event marking the document as initialized popJSLibraryManager.execute('documentInitialized'); }, ... }; })(jQuery);

runJSMethods işlevi, en üstteki modül olan pageSection'dan başlayarak her modül için tanımlanan JavaScript yöntemlerini yürütür ve ardından tüm iç blokları ve bunların iç modülleri için satır aşağısına iner:

 (function($){ window.popManager = { ... runJSMethods : function(pageSection, configuration) { // Initialize the heap with "modules", starting from the top one, and recursively iterate over its inner modules var heap = [pageSection.data('module')], i; while (heap.length > 0) { // Get the first element of the heap var module = heap.pop(); // The configuration for that module contains which JavaScript methods to execute, and which are the module's inner modules var moduleConfiguration = configuration[module]; // The list of all JavaScript functions that must be executed on the module's newly created DOM elements var jsMethods = moduleConfiguration['js-methods']; // Get all of the elements added to the DOM for that module, which have been stored in JavaScript object `popJSRuntimeManager` upon creation var elements = popJSRuntimeManager.getDOMElements(module); // Iterate through all of the JavaScript methods and execute them, passing the elements as argument for (i = 0; i < jsMethods.length; i++) { popJSLibraryManager.execute(jsMethods[i], elements); } // Finally, add the inner-modules to the heap heap = heap.concat(moduleConfiguration['inner-modules']); } }, }; })(jQuery);

Özetle, PoP'da JavaScript yürütmesi gevşek bir şekilde bağlanmıştır: Sabit bağımlılıklar yerine JavaScript işlevlerini, herhangi bir JavaScript nesnesinin abone olabileceği kancalar aracılığıyla yürütürüz.

Web Sayfaları ve API'ler

Bir PoP web sitesi, kendi kendine tüketilen bir API'dir. PoP'de bir web sayfası ile API arasında bir ayrım yoktur: Her URL varsayılan olarak web sayfasını döndürür ve yalnızca output=json parametresini ekleyerek bunun yerine API'sini döndürür (örneğin, getpop.org/en/ web sayfasıdır ve getpop.org/en/?output=json onun API'sidir). API, PoP'da dinamik olarak içerik oluşturmak için kullanılır; bu nedenle, başka bir sayfaya giden bir bağlantıya tıklandığında, talep edilen şey API'dir, çünkü o zamana kadar web sitesinin çerçevesi (üst ve yan gezinme gibi) yüklenmiş olacaktır - o zaman API modu için gereken kaynak seti web sayfasından bunun bir alt kümesi olun. Bir rota için bağımlılıkları hesaplarken bunu dikkate almamız gerekecek: Web sitesini ilk yüklerken rotayı yüklemek veya bir bağlantıya tıklayarak dinamik olarak yüklemek, farklı gerekli varlık kümeleri üretecektir.

Bunlar, kod bölmenin tasarımını ve uygulamasını tanımlayacak olan PoP'nin en önemli yönleridir. Bir sonraki adıma geçelim.

1. Varlık Bağımlılıklarını Eşleme

Her JavaScript dosyası için açık bağımlılıklarını detaylandıran bir yapılandırma dosyası ekleyebiliriz. Ancak bu, kodu çoğaltacak ve tutarlı olması zor olacaktır. Daha temiz bir çözüm, JavaScript dosyalarını tek gerçek kaynak olarak tutmak, kodu içlerinden çıkarmak ve ardından bağımlılıkları yeniden oluşturmak için bu kodu analiz etmek olacaktır.

Eşlemeyi yeniden oluşturabilmek için JavaScript kaynak dosyalarında aradığımız meta veriler şunlardır:

  • this.runJSMethods(...) gibi dahili yöntem çağrıları;
  • popJSRuntimeManager.getDOMElements(...) gibi harici yöntem çağrıları;
  • onu uygulayan tüm nesnelerde bir JavaScript işlevi yürüten popJSLibraryManager.execute(...) tüm oluşumları;
  • hangi JavaScript nesnelerinin hangi JavaScript yöntemlerini uyguladığını elde etmek için tüm popJSLibraryManager.register(...) oluşumları.

JavaScript kaynak dosyalarımızı PHP'de belirtmek ve meta verileri çıkarmak için jParser ve jTokenizer'ı aşağıdaki gibi kullanacağız:

  • Dahili yöntem çağrıları ( this.runJSMethods gibi) aşağıdaki sıra bulunurken çıkarılır: belirteç this veya that + . + dahili yöntemin adı olan başka bir belirteç ( runJSMethods ).
  • Harici yöntem çağrıları ( popJSRuntimeManager.getDOMElements gibi) aşağıdaki sıra bulunurken çıkarılır: uygulamamızdaki tüm JavaScript nesneleri listesinde bulunan bir belirteç (bu listeye önceden ihtiyacımız olacak; bu durumda, popJSRuntimeManager nesnesini içerecektir) + . + harici yöntemin adı olan başka bir belirteç ( getDOMElements ).
  • popJSLibraryManager.execute("someFunctionName") bulduğumuzda Javascript yöntemini someFunctionName olarak çıkarırız.
  • popJSLibraryManager.register(someJSObject, ["someFunctionName1", "someFunctionName2"]) bulduğumuz zaman, someFunctionName1 , someFunctionName2 yöntemlerini uygulamak için Javascript nesnesi someJSObject someFunctionName2

Senaryoyu uyguladım ama burada açıklamayacağım. (Çok uzun olması fazla bir değer katmaz, ancak PoP deposunda bulunabilir). Web sitesinin geliştirme sunucusunda bir dahili sayfa istendiğinde çalışan komut dosyası (yöntemi hakkında daha önceki bir makalede hizmet çalışanları hakkında yazmıştım), eşleme dosyasını oluşturacak ve sunucuda depolayacaktır. Oluşturulan mapping dosyasının bir örneğini hazırladım. Aşağıdaki öznitelikleri içeren basit bir JSON dosyasıdır:

  • internalMethodCalls
    Her JavaScript nesnesi için dahili işlevlerden bağımlılıkları kendi aralarında listeleyin.
  • externalMethodCalls
    Her JavaScript nesnesi için, dahili işlevlerden diğer JavaScript nesnelerinden işlevlere olan bağımlılıkları listeleyin.
  • publicMethods
    Tüm kayıtlı yöntemleri ve her bir yöntem için hangi JavaScript nesnelerinin onu uyguladığını listeleyin.
  • methodExecutions
    Her JavaScript nesnesi ve her dahili işlev için popJSLibraryManager.execute('someMethodName') aracılığıyla yürütülen tüm yöntemleri listeleyin.

Lütfen sonucun henüz bir varlık bağımlılığı haritası olmadığını, bunun yerine bir JavaScript nesne bağımlılığı haritası olduğunu unutmayın. Bu haritadan, bir nesneden bir işlev yürütüldüğünde, başka hangi nesnelerin de gerekli olacağını belirleyebiliriz. Yine de tüm varlıklar için her bir varlıkta hangi JavaScript nesnelerinin bulunduğunu yapılandırmamız gerekiyor (jTokenizer komut dosyasında, JavaScript nesneleri, harici yöntem çağrılarını tanımlamak için aradığımız belirteçlerdir, bu nedenle bu bilgi komut dosyasına bir girdidir ve kaynak dosyalardan elde edilemez). Bu, resourceloader-processor.php gibi ResourceLoaderProcessor PHP nesneleri aracılığıyla yapılır.

Son olarak, haritayı ve konfigürasyonu birleştirerek, uygulamadaki her rota için gerekli tüm varlıkları hesaplayabileceğiz.

2. Tüm Uygulama Yollarının Listelenmesi

Uygulamamızda mevcut olan tüm rotaları tanımlamamız gerekiyor. Bir WordPress web sitesi için bu liste, şablon hiyerarşilerinin her birinin URL'siyle başlar. PoP için uygulananlar şunlardır:

  • ana sayfa: https://getpop.org/en/
  • yazar: https://getpop.org/en/u/leo/
  • tek: https://getpop.org/en/blog/new-feature-code-splitting/
  • etiket: https://getpop.org/en/tags/internet/
  • sayfa: https://getpop.org/en/philosophy/
  • kategori: https://getpop.org/en/blog/ (kategori, URL yolundan category/ kaldırmak için aslında bir sayfa olarak uygulanır)
  • 404: https://getpop.org/en/this-page-does-not-exist/

Bu hiyerarşilerin her biri için, benzersiz bir konfigürasyon üreten (yani benzersiz bir varlık seti gerektirecek) tüm rotaları elde etmeliyiz. PoP durumunda, aşağıdakilere sahibiz:

  • ana sayfa ve 404 benzersizdir.
  • Etiket sayfaları, herhangi bir etiket için her zaman aynı yapılandırmaya sahiptir. Böylece, herhangi bir etiket için tek bir URL yeterli olacaktır.
  • Tek gönderi, gönderi türünün ("etkinlik" veya "gönderi" gibi) ve gönderinin ana kategorisinin ("blog" veya "makale" gibi) birleşimine bağlıdır. Ardından, bu kombinasyonların her biri için bir URL'ye ihtiyacımız var.
  • Bir kategori sayfasının yapılandırması kategoriye bağlıdır. Bu nedenle, her gönderi kategorisinin URL'sine ihtiyacımız olacak.
  • Bir yazar sayfası, yazarın rolüne ("birey", "kuruluş" veya "topluluk") bağlıdır. Bu nedenle, her biri bu rollerden birine sahip olan üç yazar için URL'lere ihtiyacımız olacak.
  • Her sayfanın kendi konfigürasyonu olabilir (“oturum aç”, “bize ulaşın”, “görevimiz” vb.). Bu nedenle, tüm sayfa URL'leri listeye eklenmelidir.

Gördüğümüz gibi, liste zaten oldukça uzun. Ek olarak, uygulamamız URL'ye yapılandırmayı değiştiren parametreler ekleyebilir, bu da potansiyel olarak hangi varlıkların gerekli olduğunu da değiştirebilir. Örneğin PoP, aşağıdaki URL parametrelerini eklemeyi teklif eder:

  • sekmesi ( ?tab=… ), ilgili bir bilgiyi göstermek için: https://getpop.org/en/blog/new-feature-code-splitting/?tab=authors;
  • format ( ?format=… ), verilerin görüntülenme şeklini değiştirmek için: https://getpop.org/en/blog/?format=list;
  • Hedef ( ?target=… ), sayfayı farklı bir sayfada açmak için Bölüm: https://getpop.org/en/add-post/?target=addons.

Başlangıç ​​rotalarından bazıları, geniş bir kombinasyon dizisi oluşturarak yukarıdaki parametrelerden bir, iki veya hatta üçüne sahip olabilir:

  • tek gönderi: https://getpop.org/en/blog/new-feature-code-splitting/
  • tek gönderinin yazarları: https://getpop.org/en/blog/new-feature-code-splitting/?tab=authors
  • bir liste olarak tek gönderinin yazarları: https://getpop.org/en/blog/new-feature-code-splitting/?tab=authors&format=list
  • kalıcı bir pencerede bir liste olarak tek gönderinin yazarları: https://getpop.org/en/blog/new-feature-code-splitting/?tab=authors&format=list&target=modals

Özetle, PoP için tüm olası yollar aşağıdaki öğelerin bir kombinasyonudur:

  • tüm ilk şablon hiyerarşi yolları;
  • hiyerarşinin farklı bir konfigürasyon üreteceği tüm farklı değerler;
  • her hiyerarşi için tüm olası sekmeler (farklı hiyerarşiler farklı sekme değerlerine sahip olabilir: Tek bir gönderide "yazarlar" ve "yanıtlar" sekmeleri olabilirken, bir yazar "gönderiler" ve "takipçiler" sekmelerine sahip olabilir);
  • her sekme için tüm olası biçimler (farklı sekmelere farklı biçimler uygulanabilir: "yazarlar" sekmesi "harita" biçimine sahip olabilir, ancak "yanıtlar" sekmesi olmayabilir);
  • Sayfayı gösteren tüm olası hedefler Her rotanın görüntülenebileceği bölümler (ana bölümde veya kayan bir pencerede bir gönderi oluşturulabilirken, “Arkadaşlarınızla paylaşın” sayfası kalıcı bir pencerede açılacak şekilde ayarlanabilir).

Bu nedenle, biraz karmaşık bir uygulama için, tüm rotaları içeren listeyi oluşturmak manuel olarak yapılamaz. O halde, bu bilgiyi veritabanından çıkarmak, işlemek ve son olarak da gereken biçimde çıktısını almak için bir komut dosyası oluşturmalıyız. Bu komut dosyası, tüm farklı kategori sayfası URL'lerinin listesini üretebileceğimiz tüm gönderi kategorilerini alacak ve ardından her kategori için, aynı altındaki herhangi bir gönderi için veritabanını sorgulayacak, bu da tek bir URL için URL üretecektir. her kategori altında yayınlayın, vb. Tam komut dosyası, hiyerarşi durumlarının her biri tarafından uygulanacak kancaları ortaya çıkaran function get_resources() başlayarak kullanılabilir.

3. Her Rota için Hangi Varlıkların Gerekli Olduğunu Tanımlayan Listeyi Oluşturma

Artık varlık-bağımlılık haritasına ve uygulamadaki tüm rotaların listesine sahibiz. Şimdi bu ikisini birleştirmenin ve her bir rota için hangi varlıkların gerekli olduğunu gösteren bir liste oluşturmanın zamanı geldi.

Bu listeyi oluşturmak için aşağıdaki prosedürü uyguluyoruz:

  1. Her rota için yürütülecek tüm JavaScript yöntemlerini içeren bir liste oluşturun:
    Rotanın modüllerini hesaplayın, ardından her modül için konfigürasyonu elde edin, ardından modülün yürütmesi gereken JavaScript işlevlerini konfigürasyondan çıkarın ve hepsini bir araya ekleyin.
  2. Ardından, her JavaScript işlevi için varlık-bağımlılık haritasında gezinin, gerekli tüm bağımlılıkların listesini toplayın ve hepsini bir araya ekleyin.
  3. Son olarak, her modülü o rotanın içinde işlemek için gereken Gidon şablonlarını ekleyin.

Ek olarak, daha önce belirtildiği gibi, her URL'nin web sayfası ve API modları vardır, bu nedenle yukarıdaki prosedürü her mod için bir kez olmak üzere iki kez çalıştırmamız gerekir (yani API modu için rotayı temsil eden URL'ye bir kez output=json parametresini ekleyerek, ve bir kez web sayfası modu için URL'yi değiştirmeden). Daha sonra farklı kullanımları olan iki liste üreteceğiz:

  1. Web sayfası modu listesi, web sitesi ilk yüklenirken kullanılacaktır, böylece bu rota için karşılık gelen komut dosyaları ilk HTML yanıtına dahil edilecektir. Bu liste sunucuda saklanacaktır.
  2. API modu listesi, web sitesinde bir sayfa dinamik olarak yüklenirken kullanılacaktır. Bu liste, uygulamanın bir bağlantı tıklandığında talep üzerine hangi ekstra varlıkların yüklenmesi gerektiğini hesaplamasını sağlamak için istemciye yüklenecektir.

Mantığın büyük kısmı, function add_resources_from_settingsprocessors($fetching_json, ...) başlayarak uygulanmıştır (depoda bulabilirsiniz). $fetching_json parametresi, web sayfası ( false ) ve API ( true ) modları arasında ayrım yapar.

Web sayfası modu için komut dosyası çalıştırıldığında, aşağıdaki özelliklere sahip bir JSON nesnesi olan resourceloader-bundle-mapping.json çıktısını verir:

  • bundle-ids
    Bu, bir paket kimliği altında gruplandırılmış en fazla dört kaynağın (adları üretim ortamı için karıştırılmıştır: eq => handlebars , er => handlebars-helpers , vb.) içeren bir koleksiyondur.
  • bundlegroup-ids
    Bu, bundle-ids bir koleksiyonudur. Her paketGrubu benzersiz bir kaynak kümesini temsil eder.
  • key-ids
    Bu, rotalar (bir rotayı benzersiz yapan tüm öznitelikler kümesini tanımlayan hashleriyle temsil edilir) ve bunlara karşılık gelen paketGroup arasındaki eşlemedir.

Görülebileceği gibi, bir rota ve kaynakları arasındaki haritalama düz değildir. key-ids bir kaynak listesiyle eşlemek yerine, bunları, kendisi bir paket listesi olan benzersiz bir bundles ile eşler ve yalnızca her paket, bir resources listesidir (her pakette en fazla dört öğeden oluşur). Neden böyle yapıldı? Bu iki amaca hizmet eder:

  1. Benzersiz bir paket Grubu altındaki tüm kaynakları tanımlamamızı sağlar. Böylece, HTML yanıtına tüm kaynakları dahil etmek yerine, karşılık gelen tüm kaynaklar içinde bir araya gelen, karşılık gelen paketGroup dosyası olan benzersiz bir JavaScript varlığı ekleyebiliyoruz. Bu, hala HTTP/2'yi desteklemeyen cihazlara hizmet verirken kullanışlıdır ve aynı zamanda yükleme süresini de artıracaktır, çünkü tek bir paketlenmiş dosyayı Gzip'lemek, kurucu dosyaları kendi başlarına sıkıştırıp sonra onları bir araya getirmekten daha etkilidir. Alternatif olarak, kaynaklar ve packageGroups arasında bir uzlaşma olan benzersiz bir packageGroup yerine bir dizi demet de yükleyebiliriz (demetlerin yüklenmesi Gzip'leme nedeniyle packageGroups'tan daha yavaştır, ancak geçersiz kılma sık sık oluyorsa bu daha performanslıdır, bu nedenle biz paketGroup'un tamamını değil, yalnızca güncellenmiş paketi indirir). Tüm kaynakları paketler ve paket Grupları halinde gruplamak için kullanılan komut dosyaları filegenerator-bundles.php ve filegenerator-bundlegroups.php içinde bulunur.
  2. Kaynak kümelerini demetlere bölmek, ortak kalıpları belirlememize (örneğin, birçok yol arasında paylaşılan dört kaynak kümesini tanımlamamıza) olanak tanır ve sonuç olarak farklı yolların aynı pakete bağlanmasına izin verir. Sonuç olarak, oluşturulan liste daha küçük bir boyuta sahip olacaktır. Bu, sunucuda yaşayan web sayfası listesi için pek kullanışlı olmayabilir, ancak daha sonra göreceğimiz gibi istemciye yüklenecek olan API listesi için harika.

API modu için komut dosyası çalıştırıldığında, aşağıdaki özelliklerle resource.js dosyasının çıktısını verir:

  • bundles ve bundle-groups , web sayfası modu için belirtilenle aynı amaca hizmet eder
  • keys ayrıca web sayfası modu için key-ids aynı amaca hizmet eder. Ancak, rotayı temsil eden anahtar olarak bir hash'e sahip olmak yerine, bir rotayı benzersiz yapan tüm bu niteliklerin bir birleşimidir - bizim durumumuzda format ( f ), sekme ( t ) ve hedef ( r ).
  • sources , her kaynağın kaynak dosyasıdır.
  • types her kaynak için CSS veya JavaScript'tir (her ne kadar basitlik adına bu makalede JavaScript kaynaklarının CSS kaynaklarını bağımlılıklar olarak ayarlayabileceğini ve modüllerin aşamalı CSS yükleme stratejisini uygulayarak kendi CSS varlıklarını yükleyebileceğini bu makalede ele almadık). ).
  • resources , her hiyerarşi için hangi paket Gruplarının yüklenmesi gerektiğini yakalar.
  • order ordered-load-resources , komut dosyalarının bağımlı komut dosyalarından önce yüklenmesini önlemek için hangi kaynakların sırayla yüklenmesi gerektiğini içerir (varsayılan olarak zaman uyumsuzdur).

Bu dosyanın nasıl kullanılacağını bir sonraki bölümde inceleyeceğiz.

4. Varlıkları Dinamik Olarak Yükleme

Belirtildiği gibi, kullanıcı bir bağlantıya tıkladıktan hemen sonra bir rota için gerekli varlıkları yüklemeye başlayabilmemiz için API listesi istemciye yüklenecektir.

Eşleme Komut Dosyasını Yükleme

Uygulamadaki tüm rotalar için kaynak listesi içeren oluşturulan JavaScript dosyası hafif değil - bu durumda 85 KB'ye çıktı (kendisi optimize edilmiştir, kaynak adlarını karıştırmış ve rotalar arasındaki ortak kalıpları tanımlamak için paketler oluşturmuştur) . Ayrıştırma süresi büyük bir darboğaz olmamalıdır, çünkü JSON'u ayrıştırmak aynı veriler için JavaScript'i ayrıştırmaktan 10 kat daha hızlıdır. Ancak, boyut ağ aktarımından kaynaklanan bir sorundur, bu nedenle bu betiği uygulamanın algılanan yükleme süresini etkilemeyecek veya kullanıcıyı bekletmeyecek şekilde yüklememiz gerekir.

Uyguladığım çözüm, bu dosyayı hizmet çalışanları kullanarak önceden önbelleğe almak, kritik JavaScript yöntemlerini yürütürken ana iş parçacığını engellememesi için defer kullanarak yüklemek ve ardından kullanıcı bir bağlantıya tıkladığında bir geri dönüş bildirim mesajı göstermektir. komut dosyası yüklenmeden önce: "Web sitesi hala yükleniyor, lütfen bağlantılara tıklamak için birkaç dakika bekleyin." Bu, komut dosyaları yüklenirken her şeyin üstüne bir yükleme loadingscreen sınıfı yerleştirilmiş sabit bir div eklenerek ve ardından div içine bir notificationmsg sınıfıyla birlikte bildirim iletisi ve bu birkaç CSS satırı eklenerek gerçekleştirilir:

 .loadingscreen > .notificationmsg { display: none; } .loadingscreen:focus > .notificationmsg, .loadingscreen:active > .notificationmsg { display: block; }

Başka bir çözüm, bu dosyayı birkaç dosyaya bölmek ve gerektiğinde aşamalı olarak yüklemektir (zaten kodladığım bir strateji). Ayrıca 85 KB'lık dosya, "yazarın duyuruları, küçük resimlerle gösterilen, modals penceresinde görüntülenen" gibi, mümkünse her mavi ayda bir kez erişilebilen rotalar da dahil olmak üzere, uygulamadaki tüm olası rotaları içerir. Çoğunlukla erişilen rotalar çok az sayıdadır (ana sayfa, tek, yazar, etiket ve tüm sayfalar, hepsi ekstra nitelik içermez), bu da 30 KB civarında çok daha küçük bir dosya üretmelidir.

İstenen URL'den Rota Alma

İstenen URL'den rotayı tanımlayabilmemiz gerekir. Örneğin:

  • https://getpop.org/en/u/leo/ “yazar” rotasıyla eşleşir,
  • https://getpop.org/en/u/leo/?tab=followers “yazarın takipçileri” rotasına haritalar,
  • https://getpop.org/en/tags/internet/ “etiket” rotasına eşlenir,
  • https://getpop.org/en/tags/ “page /tags/ ” rotasına haritalar,
  • ve bunun gibi.

Bunu başarmak için, URL'yi değerlendirmemiz ve ondan bir rotayı benzersiz kılan unsurları çıkarmamız gerekecek: hiyerarşi ve tüm nitelikler (format, sekme ve hedef). Nitelikleri tanımlamak sorun değil çünkü bunlar URL'deki parametreler. Tek zorluk, URL'yi çeşitli kalıplarla eşleştirerek URL'den hiyerarşiyi (ev, yazar, tek, sayfa veya etiket) çıkarmaktır. Örneğin,

  • https://getpop.org/en/u/ ile başlayan her şey yazardır.
  • https://getpop.org/en/tags/ ile başlayan ancak tam olarak https://getpop.org/en/tags/ ile başlayan her şey bir etikettir. Tam olarak https://getpop.org/en/tags/ ise, o zaman bir sayfadır.
  • Ve bunun gibi.

Resourceloader.js'nin 321. satırından başlayarak uygulanan aşağıdaki işlev, tüm bu hiyerarşiler için kalıplarla bir konfigürasyonla beslenmelidir. Önce URL'de alt yol olup olmadığını kontrol eder - bu durumda "ev" olur. Ardından “yazar”, “etiket” ve “tek” hiyerarşilerini tek tek kontrol eder. Bunlardan herhangi birinde başarılı olmazsa, varsayılan durum “sayfa”dır:

 window.popResourceLoader = { // The config will be populated externally, using a config.js file, generated by a script config : {}, getPath : function(url) { var parser = document.createElement('a'); parser.href = url; return parser.pathname; }, getHierarchy : function(url) { var path = this.getPath(url); if (!path) { return 'home'; } var config = this.config; if (path.startsWith(config.paths.author) && path != config.paths.author) { return 'author'; } if (path.startsWith(config.paths.tag) && path != config.paths.tag) { return 'tag'; } // We must also check that this path is, itself, not a potential page (https://getpop.org/en/posts/articles/ is "page", but https://getpop.org/en/posts/this-is-a-post/ is "single") if (config.paths.single.indexOf(path) === -1 && config.paths.single.some(function(single_path) { return path.startsWith(single_path) && path != single_path;})) { return 'single'; } return 'page'; }, ... };

Gerekli tüm veriler zaten veritabanında olduğundan (tüm kategoriler, tüm sayfa bilgileri vb.), bu yapılandırma dosyasını bir geliştirme veya hazırlama ortamında otomatik olarak oluşturmak için bir komut dosyası çalıştıracağız. The implemented script is resourceloader-config.php, which produces config.js with the URL patterns for the hierarchies “author”, “tag” and “single”, under the key “paths”:

 popResourceLoader.config = { "paths": { "author": "u/", "tag": "tags/", "single": ["posts/articles/", "posts/announcements/", ...] }, ... };

Loading Resources for the Route

Once we have identified the route, we can obtain the required assets from the generated JavaScript file under the key “resources”, which looks like this:

 config.resources = { "home": { "1": [1, 110, ...], "2": [2, 111, ...], ... }, "author": { "7": [6, 114, ...], "8": [7, 114, ...], ... }, "tag": { "119": [66, 127, ...], "120": [66, 127, ...], ... }, "single": { "posts/": { "7": [190, 142, ...], "3": [190, 142, ...], ... }, "events/": { "7": [213, 389, ...], "3": [213, 389, ...], ... }, ... }, "page": { "log-in/": { "3": [233, 115, ...] }, "log-out/": { "3": [234, 115, ...] }, "add-post/": { "3": [239, 398, ...] }, "posts/": { "120": [268, 127, ...], "122": [268, 127, ...], ... }, ... } };

At the first level, we have the hierarchy (home, author, tag, single or page). Hierarchies are divided into two groups: those that have only one set of resources (home, author and tag), and those that have a specific subpath (page permalink for the pages, custom post type or category for the single). Finally, at the last level, for each key ID (which represents a unique combination of the possible values of “format”, “tab” and “target”, stored under “keys”), we have an array of two elements: [JS bundleGroup ID, CSS bundleGroup ID], plus additional bundleGroup IDs if executing progressive booting (JS bundleGroups to be loaded as "async" or "defer" are bundled separately; this will be explained in the optimizations section below).

Please note: For the single hierarchy, we have different configurations depending on the custom post type. This can be reflected in the subpath indicated above (for example, events and posts ) because this information is in the URL (for example, https://getpop.org/en/posts/the-winners-of-climate-change-techno-fixes/ and https://getpop.org/en/events/debate-post-fork/ ), so that, when clicking on a link, we will know the corresponding post type and can thus infer the corresponding route. However, this is not the case with the author hierarchy. As indicated earlier, an author may have three different configurations, depending on the user role ( individual , organization or community ); however, in this file, we've defined only one configuration for the author hierarchy, not three. That is because we are not able to tell from the URL what is the role of the author: user leo (under https://getpop.org/en/u/leo/ ) is an individual, whereas user pop (under https://getpop.org/en/u/pop/ ) is a community; however, their URLs have the same pattern. If we could instead have the URLs https://getpop.org/en/u/individuals/leo/ and https://getpop.org/en/u/communities/pop/ , then we could add a configuration for each user role. However, I've found no way to achieve this in WordPress. As a consequence, only for the API mode, we must merge the three routes (individuals, organizations and communities) into one, which will have all of the resources for the three cases; and clicking on the link for user leo will also load the resources for organizations and communities, even if we don't need them.

Finally, when a URL is requested, we obtain its route, from which we obtain the bundleGroup IDs (for both JavaScript and CSS assets). From each bundleGroup, we find the corresponding bundles under bundlegroups . Then, for each bundle, we obtain all resources under the key bundles . Finally, we identify which assets have not yet been loaded, and we load them by getting their source, which is stored under the key sources . The whole logic is coded starting from line 472 in resourceloader.js.

And with that, we have implemented code-splitting for our application! From now on, we can get better loading times by applying optimizations. Let's tackle that next.

5. Applying Optimizations

The objective is to load as little code as possible, as delayed as possible, and to cache as much of it as possible. Let's explore how to do this.

Splitting Up the Code Into Smaller Units

A single JavaScript asset may implement several functions (by calling popJSLibraryManager.register ), yet maybe only one of those functions is actually needed by the route. Thus, it makes sense to split up the asset into several subassets, implementing a single function on each of them, and extracting all common code from all of the functions into yet another asset, depended upon by all of them.

For instance, in the past, there was a unique file, waypoints.js , that implemented the functions waypointsFetchMore , waypointsTheater and a few more. However, in most cases, only the function waypointsFetchMore was needed, so I was loading the code for the function waypointsTheater unnecessarily. Then, I split up waypoints.js into the following assets:

  • waypoints.js, with all common code and implementing no public functions;
  • waypoints-fetchmore.js, which implements just the public function waypointsFetchMore ;
  • waypoints-theater.js, which implements just the public function waypointsTheater .

Evaluating how to split the files is a manual job. Luckily, there is a tool that greatly eases the task: Chrome Developer Tools' “Coverage” tab, which displays in red those portions of JavaScript code that have not been invoked:

Büyük önizleme
Büyük önizleme

By using this tool, we can better understand how to split our JavaScript files into more granular units, thus reducing the amount of unneeded code that is loaded.

Integration With Service Workers

By precaching all of the resources using service workers, we can be pretty sure that, by the time the response is back from the server, all of the required assets will have been loaded and parsed. I wrote an article on Smashing Magazine on how to accomplish this.

Progressive Booting

PoP's architecture plays very nice with the concept of loading assets in different stages. When defining the JavaScript methods to execute on each module (by doing $this->add_jsmethod($methods, 'calendar') ), these can be set as either critical or non-critical . By default, all methods are set as non-critical, and critical methods must be explicitly defined by the developer, by adding an extra parameter: $this->add_jsmethod($methods, 'calendar', 'critical') . Then, we will be able to load scripts immediately for critical functions, and wait until the page is loaded to load non-critical functions, the JavaScript files of which are loaded using defer .

 (function($){ window.popManager = { init : function() { var that = this; $.each(this.configuration, function(pageSectionId, configuration) { ... this.runJSMethods(pageSection, configuration, 'critical'); ... }); window.addEventListener('load', function() { $.each(this.configuration, function(pageSectionId, configuration) { ... this.runJSMethods(pageSection, configuration, 'non-critical'); ... }); }); ... }, ... }; })(jQuery);

The gains from progressive booting are major: The JavaScript engine needs not spend time parsing non-critical JavaScript initially, when a quick response to the user is most important, and overall reduces the time to interactive.

Testing And Analizying Performance Gains

We can use https://getpop.org/en/, a PoP website, for testing purposes. When loading the home page, opening Chrome Developer Tools' “Elements” tab and searching for “defer”, it shows 4 occurrences. Thanks to progressive booting, that is 4 bundleGroup JavaScript files containing the contents of 57 Javascript files with non-critical methods that could wait until the website finished loading to be loaded:

Büyük önizleme

If we now switch to the “Network” tab and click on a link, we can see which assets get loaded. For instance, click on the link “Application/UX Features” on the left side. Filtering by JavaScript, we see it loaded 38 files, including JavaScript libraries and Handlebars templates. Filtering by CSS, we see it loaded 9 files. These 47 files have all been loaded on demand:

Büyük önizleme

Let's check whether the loading time got boosted. We can use WebPagetest to measure the application with and without code-splitting, and calculate the difference.

  • Without code-splitting: testing URL, WebPagetest results
Büyük önizleme
  • With code-splitting, loading resources: testing URL, WebPagetest Results
Büyük önizleme
  • With code-splitting, loading a bundleGroup: testing URL, WebPagetest Results
Büyük önizleme

We can see that when loading the app bundle with all resources or when doing code-splitting and loading resources, there is not so much gain. However, when doing code-splitting and loading a bundleGroup, the gains are significant: 1.7 seconds in loading time, 500 milliseconds to the first meaningful paint, and 1 second to interactive.

Conclusion: Is It Worth It?

You might be thinking, Is it worth it all this trouble? Let's analyze the advantages and disadvantages of implementing our own code-splitting features.

Dezavantajları

  • Onu korumak zorundayız.
    Webpack'i yeni kullansaydık, yazılımı güncel tutmak için topluluğuna güvenebilir ve eklenti ekosisteminden yararlanabilirdik.
  • Komut dosyalarının çalışması zaman alır.
    PoP web sitesi Agenda Urbana, 422 set benzersiz kaynak ürettiği 304 farklı rotaya sahiptir. Bu web sitesi için, varlık bağımlılığı haritasını oluşturan komut dosyasını 2012'den bir MacBook Pro kullanarak çalıştırmak yaklaşık 8 dakika sürer ve tüm kaynaklarla listeleri oluşturan ve paket ve bundleGroup dosyalarını oluşturan komut dosyasını çalıştırmak 15 dakika sürer. . Bu bir kahve içmek için fazlasıyla yeterli!
  • Bir evreleme ortamı gerektirir.
    Komut dosyalarını çalıştırmak için yaklaşık 25 dakika beklememiz gerekirse, onu üretimde çalıştıramayız. Üretim sistemiyle tamamen aynı konfigürasyona sahip bir hazırlama ortamına ihtiyacımız var.
  • Web sitesine sadece yönetim için ekstra kod eklenir.
    85 KB'lık kod tek başına işlevsel değildir, sadece diğer kodları yönetmek için kodlar.
  • Karmaşıklık eklendi.
    Varlıklarımızı daha küçük birimlere bölmek istiyorsak, bu her durumda kaçınılmazdır. Web paketi ayrıca uygulamaya karmaşıklık katacaktır.

Avantajlar

  • WordPress ile çalışır.
    Webpack, WordPress ile kutudan çıktığı gibi çalışmaz ve çalışmasını sağlamak için biraz geçici çözüm gerekir. Bu çözüm, WordPress için kutudan çıktığı gibi çalışır (PoP kurulu olduğu sürece).
  • Ölçeklenebilir ve genişletilebilir.
    JavaScript dosyaları isteğe bağlı olarak yüklendiğinden, uygulamanın boyutu ve karmaşıklığı sınırsız büyüyebilir.
  • Gutenberg'i (yarının WordPress'i olarak da bilinir) destekler.
    JavaScript çerçevelerini talep üzerine yüklememize izin verdiği için, geliştirici tarafından seçilen çerçevede kodlanması beklenen Gutenberg bloklarını (Gutenblocks olarak adlandırılır) destekleyecek ve aynı uygulama için farklı çerçevelere ihtiyaç duyulmasının potansiyel sonucu olacaktır.
  • Bu kullanışlı.
    Yapı aracı, yapılandırma dosyalarının oluşturulmasıyla ilgilenir. Beklemek dışında ekstra bir çaba göstermemize gerek yok.
  • Optimizasyonu kolaylaştırır.
    Şu anda, bir WordPress eklentisi JavaScript varlıklarını seçici olarak yüklemek istiyorsa, sayfa kimliğinin doğru olup olmadığını kontrol etmek için çok sayıda koşul kullanacaktır. Bu araçla buna gerek yok; süreç otomatiktir.
  • Uygulama daha hızlı yüklenecektir.
    Bu aracı kodlamamızın tüm nedeni buydu.
  • Bir evreleme ortamı gerektirir.
    Olumlu bir yan etki artan güvenilirliktir: Komut dosyalarını üretimde çalıştırmayacağız, bu yüzden orada hiçbir şeyi bozmayacağız; dağıtım işlemi beklenmeyen davranışlardan dolayı başarısız olmaz; ve geliştirici, üretimdekiyle aynı yapılandırmayı kullanarak uygulamayı test etmeye zorlanacak.
  • Uygulamamıza göre özelleştirilmiştir.
    Ek yük veya geçici çözüm yoktur. Çalıştığımız mimariye dayanarak elde ettiğimiz şey tam olarak ihtiyacımız olan şeydir.

Sonuç olarak: evet, buna değer, çünkü artık WordPress web sitemize isteğe bağlı olarak yük varlıkları uygulayabiliyor ve daha hızlı yüklenmesini sağlayabiliyoruz.

Diğer Kaynaklar

  • “Kod Bölme” kılavuzu dahil Web paketi
  • “Daha İyi Web Paketi Yapıları” (video), K. Adam White
    Webpack'in WordPress ile Entegrasyonu
  • “Gutenberg ve Yarının WordPress'i,” Morten Rand-Hendriksen, WP Tavern
  • “WordPress, Gutenberg Blokları Oluşturmak için JavaScript Çerçevesinden Agnostik Bir Yaklaşımı Keşfediyor,” Sarah Gooding, WP Tavern