WordPress'te JavaScript Cehennemdir… ve İşte Nedeni

Yayınlanan: 2022-05-05

WordPress geliştirme yığını son yıllarda çok değişti. Gutenberg'in gelişinden bu yana, JavaScript'in favori CMS'mizdeki rolü her zamankinden daha önemli. Bu blogda, bunun geliştiriciler için sağladığı avantajlar hakkında uzun uzadıya konuştuk (birkaç örnek vermek gerekirse, Gutenberg uzantıları, TypeScript ve React hakkında tavsiyeler, geliştirme araçları, eklenti örnekleri ve daha fazlası hakkında konuştuk) ama her biri hikayenin karanlık bir tarafı var… ve bugün burada bunun hakkında konuşacağız.

Bugünün gönderisinde, bir WordPress eklenti geliştiricisi olarak gelecekte karşılaşabileceğiniz 3 ana sorunu sizinle paylaşacağım. İşin komik yanı, her birinin farklı bir suçlusu var: ya WordPress'in kendisi, diğer geliştiriciler ya da kendiniz. İşte başlıyoruz: Karşılaşabileceğiniz en yaygın JavaScript baş ağrıları ve bunları önlemek/düzeltmek için neler yapabileceğiniz.

Eklentinizi ve Sitenizi Bozacak 1 Numaralı WPO Eklentisi

Nelio: WPO eklentilerinde çok sayıda biletin kaynağı olan sorunla başlayalım.

Hızlı yüklenen hafif bir web sitesine sahip olmanın önemini özetleyen birçok makale ve blog yazısı okuduğunuza eminim. Yani, biz de bunun hakkında birkaç kez yazdık! Genellikle verdikleri ipuçları arasında, daha iyi bir barındırma sağlayıcısına geçmek, önbellek eklentileri ve CDN'leri kullanmak, sunucunuzu ve WordPress'i güncel tutmak veya (ve işte ilk sorun burada geliyor) bir WPO eklentisi yüklemek gibi şeyler bulacaksınız. İkincisinin bazı örnekleri şunları içerir:

  • 1 milyondan fazla yükleme ile W3 Toplam Önbellek
  • 1 milyondan fazla yüklemeye sahip SiteGround Optimizer (bu arada bunlardan birini web sitemizde kullanıyoruz)
  • İyi arkadaşımız Fernando Tellado'dan WordPress WPO Tweaks & Optimizations

Bu eklentiler, genel olarak “herhangi bir WordPress sitesinin faydalanabileceği” bir dizi mantıklı optimizasyon yoluyla web sitenizi hızlandırmayı vaat ediyor. Bu optimizasyonlar şunları içerir:

  • Emojiler veya Dashicons gibi ön uçta gereksiz komut dosyalarının kuyruğunu kaldırma
  • Sayfaları ve veritabanı sorgularını önbelleğe alma
  • Başlıkta yer alan bilgi miktarını azaltmak
  • JavaScript komut dosyalarını ve CSS stillerini birleştirme ve küçültme
  • HTML'yi küçültme
  • Statik varlıklarınızın URL'lerinden sürüm sorgusu argümanını kaldırma
  • JavaScript komut dosyalarını erteleme ve/veya bunları eşzamansız olarak yükleme
  • vb

Dediğim gibi, bu tür optimizasyonlar genel olarak faydalı olabilir. Ancak deneyimlerimize göre, bir WordPress web sitesindeki tüm JS optimizasyonları daha fazla sorunla sonuçlanma eğilimindedir, bu nedenle sözde iyileştirmelerini işe yaramaz hale getirir. Yıllar boyunca gördüğüm gerçek örnekler:

  • Komut dosyalarını birleştirme. Tarayıcınızın istemesi gereken daha az komut dosyası, daha iyi. Bu yüzden tüm komut dosyalarını tek bir komut dosyasında birleştirmek mantıklıdır. Ancak bu sorunlu olabilir. Genel olarak, bir JavaScript komut dosyası çökerse, yürütmesi sona erer ve hata tarayıcınızın konsolunda raporlanır. Ancak yalnızca bu komut dosyasının yürütülmesi durdurulur; diğer komut dosyalarınız normal şekilde çalışacaktır. Ama hepsini birleştirirseniz… peki, bir betik başarısız olur olmaz, diğer betikler (belki sizinki de dahil) çalışmaz ve kullanıcılarınız beklendiği gibi çalışmayan eklentinin sizin eklentiniz olduğunu düşünecektir.
  • Komut dosyalarını küçültme. İster inanın ister inanmayın, normal ifadeleri bozan ve sözdizimi hataları olan JS komut dosyalarıyla sonuçlanan bazı küçültme işlemleri gördüm. Elbette, bununla en son karşılaştığımdan beri bir süre geçti ama… :-/
  • Sorgu argümanları. WordPress'te bir komut dosyasını kuyruğa aldığınızda, bunu sürüm numarasıyla yapabilirsiniz (bu arada, muhtemelen @wordpress/scripts tarafından otomatik olarak oluşturulmuştur). Sürüm numaraları son derece yararlıdır: Eklentinizi güncellerseniz ve komut dosyanız değişirse, bu yeni sürüm numarası tüm ziyaretçilerin farklı bir URL görmesini garanti eder ve bu nedenle tarayıcıları yeni sürümü ister. Ne yazık ki, bir WPO eklentisi sorgu dizesini kaldırırsa, ziyaretçileriniz komut dosyasının değiştiğini fark etmeyebilir ve söz konusu komut dosyasının önbelleğe alınmış bir kopyasını kullanacaklardır… bu da istenmeyen sonuçlara neden olabilir veya olmayabilir. Lanet etmek!

Tam bir felaket, değil mi? Ama bir sonrakini duyana kadar bekleyin:

Erteleme komut dosyaları

Nelio'da, ziyaretçilerinizi izlemek ve hangi tasarımın ve içeriğin en fazla dönüşümü sağladığını keşfetmek için bir A/B Testi eklentisi uyguladık. Tahmin edebileceğiniz gibi, izleme komut dosyamız şuna benzer:

 window.NelioABTesting = window.NelioABTesting || {}; window.NelioABTesting.init = ( settings ) => { // Add event listeners to track visitor events... console.log( settings ); };

Yani, betiğin şu anda çalışmakta olan testler hakkında bilgi sahibi olmasını sağlamak için çağırmamız gereken bir init işlevini ortaya çıkarır. Bu yöntemi çağırmak için PHP'de bir satır içi betiği aşağıdaki gibi kuyruğa alıyoruz:

 function nab_enqueue_tracking_script() { wp_enqueue_script( 'nab-tracking', ... ); wp_add_inline_script( 'nab-tracking', sprintf( 'NelioABTesting.init( %s );', wp_json_encode( nab_get_tracking_settings() ) ) ); } add_action( 'wp_enqueue_scripts', 'nab_enqueue_tracking_script' );

bu da aşağıdaki HTML etiketleriyle sonuçlanır:

 <head> ... <script type="text/javascript" src="https://.../dist/tracking.js" ></script> <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ...

Ancak, WPO eklentiniz komut defer niteliğini eklediğinde ne olur?

 <head> ... <script defer <!-- This delays its loading... --> type="text/javascript" src="https://.../dist/tracking.js" ></script> <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ...

Eh, komut dosyası artık ertelendi… bu, önceki snippet'in şuna eşdeğer olduğu anlamına geliyor:

 <head> ... <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ... <script type="text/javascript" src="https://.../dist/tracking.js" ></script> </body> </html>

ve sonuç olarak, nab-tracking-js artık gerektiği zaman yüklenmez ve bu nedenle, ondan sonra gelen ve ona dayanan satır içi komut dosyası basitçe başarısız olur: nab-tracking-js-after , NelioABTesting.init kullanır. , defer yönergesi sayesinde henüz mevcut değil. Berbat!

Çözüm

En etkili çözüm açıktır: kullanıcılarınıza komut dosyası optimizasyonunu devre dışı bırakmalarını ve bir gün aramasını söyleyin. Sonuçta, JavaScript'te bağımlılık yönetimi genel olarak son derece karmaşıktır (özellikle defer ve zaman async yönergeleri kullanırsak) ve WordPress de bir istisna değildir. Konuyla ilgili bu 12 yıllık tartışmaya bir göz atın!

Ancak bu mümkün değilse (ve olmadığını biliyorum), bizim yaptığımızın aynısını yapmanızı öneririm: init yönteminden kurtulun ve normal ve satır içi komut dosyalarınızın sorumluluklarını tersine çevirin. Diğer bir deyişle, normal komut dosyasından önce bir satır içi komut dosyası ekleyin ve bunu gerekli ayarlarla global bir değişken tanımlamak için kullanın:

 function nab_enqueue_tracking_script() { wp_enqueue_script( 'nab-tracking', ... ); wp_add_inline_script( 'nab-tracking', sprintf( 'NelioABTestingSettings = %s;', wp_json_encode( nab_get_tracking_settings() ) ), 'before' ); } add_action( 'wp_enqueue_scripts', 'nab_enqueue_tracking_script' );

böylece ortaya çıkan HTML şöyle görünür:

 <head> ... <script type="text/javascript" > NelioABTestingSettings = {"experiments":[...],...}; </script> <script type="text/javascript" src="https://.../dist/tracking.js" ></script> ... </head> <body> ...

ve bu nedenle, harici komut dosyasının yürütülmesinin gecikmeli olup olmaması önemli değildir - her zaman satır içi komut dosyasından sonra görünecek ve böylece aralarındaki bağımlılık karşılanacaktır.

Son olarak, kimsenin ayarlarınızı değiştirmeyeceğinden emin olmak istiyorsanız, değişkeni const olarak bildirin ve değerini Object.freeze ile dondurun:

 ... sprintf( 'const NelioABTestingSettings = Object.freeze( %s );', wp_json_encode( nab_get_tracking_settings() ) ), ...

hangi tüm modern tarayıcılar tarafından desteklenir.

#2 WordPress'te çalışabilecek veya çalışmayabilecek bağımlılıklar…

Bağımlılık yönetimi, özellikle WordPress'in yerleşik komut dosyaları hakkında konuşurken, WordPress'te sorunlu olabilir. Açıklamama izin ver.

Örneğin, burada açıkladığımız gibi Gutenberg için küçük bir uzantı oluşturduğumuzu hayal edin. Eklentimizin kaynak kodu muhtemelen aşağıdaki gibi bazı import ifadelerine sahip olacaktır:

 import { RichTextToolbarButton } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { registerFormatType } from '@wordpress/rich-text'; // ...

Bu JS kaynak kodu aktarıldığında, Webpack (veya kullandığınız araç) tüm bağımlılıkları ve kendi kaynak kodunuzu tek bir JS dosyasında paketleyecektir. Bu, daha sonra WordPress'ten kuyruğa alacağınız dosyadır, böylece her şey beklediğiniz gibi çalışır.

Böyle bir dosya oluşturmak için @wordpress/scripts kullandıysanız, yerleşik işlem paketlerin global kapsamda mevcut olacağını varsaydığından, bazı bağımlılıklar çıktı dosyasına dahil edilmeyecektir. Bu, önceki ithalatın buna benzer bir şeye aktarılacağı anlamına gelir:

 const { RichTextToolbarButton } = window.wp.blockEditor; const { __ } = window.wp.i18n; const { registerFormatType } = window.wp.richText; // ...

Komut dosyanızın bağımlılıklarından hiçbirini kaçırmadığınızdan emin olmak için @wordpress/scripts yalnızca JS kodunuzu aktarmakla kalmaz, aynı zamanda WordPress bağımlılıklarıyla bir PHP dosyası oluşturur:

 <?php return array( 'dependencies' => array('wp-block-editor','wp-i18n','wp-rich-text'), 'version' => 'a12850ccaf6588b1e10968124fa4aba3', );

Oldukça temiz, ha? Sorun nedir? Bu WordPress paketleri sürekli geliştirme aşamasındadır ve oldukça sık değişirler, yeni özellikler ve iyileştirmeler eklerler. Bu nedenle, eklentinizi WordPress'in en son sürümünü kullanarak geliştirirseniz, yanlışlıkla o son sürümde bulunan (ve dolayısıyla her şey olması gerektiği gibi çalışır) ancak "eski" WordPress sürümlerinde olmayan işlevleri veya özellikleri kullanabilirsiniz…

Nasıl söyleyebilirsin?

Çözüm

Buradaki tavsiyem çok basit: Eklentilerinizi en son WordPress sürümünü kullanarak geliştirin, ancak sürümlerinizi eski sürümlerde test edin. Özellikle, eklentinizi en azından eklentinizin desteklemesi gereken minimum WordPress sürümüyle test etmenizi öneririm. Eklentinizin readme.txt bulacağınız minimum sürüm:

 === Nelio Content === ... Requires PHP: 7.0 Requires at least: 5.4 Tested up to: 5.9 ...

Bir WordPress sürümünden diğerine geçmek, aşağıdaki WP CLI komutunu çalıştırmak kadar kolaydır:

 wp core update --version=5.4 --force

#3 Ok işlevleri düşündüğünüzden daha zor

Son olarak, birkaç gün önce karşılaştığım ve beni deli eden en son sorunlardan birini paylaşmama izin verin. Özetle, buna benzer bir JavaScript dosyamız vardı:

 import domReady from '@wordpress/dom-ready'; domReady( () => [ ...document.querySelectorAll( '.nelio-forms-form' ) ] .forEach( initForm ) ); // Helpers // ------- const initForm = ( form ) => { ... } // ...

bu, Nelio Formlarınızı ön uçta başlatır. Senaryo oldukça basit, değil mi? DOM hazır olduğunda çağrılan anonim bir işlevi tanımlar. Bu işlev, initForm adlı bir yardımcı (ok) işlevi kullanır. Görünüşe göre, bu kadar basit bir örnek çökebilir! Ancak yalnızca bazı özel koşullar altında (örneğin, betik, defer özniteliği kullanılarak bir WPO eklentisi tarafından "optimize edilmişse").

JS, önceki komut dosyasını şu şekilde yürütür:

  1. domReady içindeki anonim işlev tanımlanır
  2. domReady çalışır
  3. DOM henüz hazır değilse (ve genellikle bir komut dosyası yüklendiğinde hazır değilse), domReady geri çağırma işlevini çalıştırmaz. Bunun yerine, daha sonra arayabilmesi için sadece kaydını tutar.
  4. JavaScript, dosyayı ayrıştırmaya devam eder ve initForm işlevini yükler
  5. DOM hazır olduğunda, son olarak geri arama işlevi çağrılır.

Şimdi, ya üçüncü adıma geldiğimizde DOM hazırsa ve bu nedenle domReady doğrudan anonim işlevi çağırırsa? Bu durumda, betik undefined bir hatayı tetikleyecektir, çünkü initForm hala undefined .

Aslında tüm bunlarla ilgili en merak edilen şey, bu iki çözümün eşdeğer olması:

 domReady( aux ); const aux = () => {};
 domReady( () => aux() ); const aux = () => {}

JavaScript linter, yalnızca ilkinde bir hata verir, ancak en sonuncusunda değil.

Çözüm

İki olası çözüm vardır: ya yardımcı işlevi function anahtar sözcüğünü kullanarak tanımlar ve ok işlevini unutursunuz ya da tüm yardımcı işlevler tanımlandıktan sonra domReady ifadesini sonuna taşırsınız:

 domReady( aux ); function aux() { // ... }
 const aux = () => { // ... }; domReady( aux );

İlk çözümün, sahip olduğumuz orijinal çözüme benziyorsa neden işe yaradığını merak ediyorsanız, tamamen JavaScript kaldırmanın nasıl çalıştığı ile ilgilidir. Kısacası, JavaScript'te bir işlevi ( function ile tanımlanmış) tanımından önce kullanabilirsiniz, ancak aynısını değişkenler ve sabitler (ve dolayısıyla ok işlevleri) ile yapamazsınız.

Özetle

JavaScript'te yanlış gidebilecek çok sayıda şey var. Neyse ki, özellikle yaptığımız işe dikkat edersek hepsinin bir çözümü var. Umarım bugün yeni bir şey öğrenmişsinizdir ve geçmişte yaptığım hatalar ve hatalar sayesinde gelecekte kendi bedeninizde acı çekmekten kaçınacağınıza inanıyorum.

Ian Stauffer tarafından Unsplash'ta öne çıkan görsel.