JavaScript di WordPress adalah Neraka… dan Inilah Alasannya

Diterbitkan: 2022-05-05

Tumpukan pengembangan WordPress telah banyak berubah dalam beberapa tahun terakhir. Sejak kedatangan Gutenberg, peran JavaScript di CMS favorit kami menjadi lebih penting dari sebelumnya. Di blog ini kami telah berbicara panjang lebar tentang keuntungan yang diperlukan untuk pengembang (untuk menyebutkan beberapa contoh, kami telah berbicara tentang ekstensi untuk Gutenberg, saran tentang TypeScript dan React, alat pengembangan, contoh plugin, dan banyak lagi) tetapi setiap cerita memiliki sisi gelapnya ... dan itulah yang akan kita bicarakan di sini hari ini.

Dalam posting hari ini, saya akan berbagi dengan Anda 3 masalah utama yang mungkin Anda, seorang pengembang plugin WordPress, hadapi di masa depan. Dan bagian yang lucu adalah, masing-masing dari mereka memiliki penyebab yang berbeda: baik WordPress itu sendiri, pengembang lain, atau diri Anda sendiri. Jadi ini dia: sakit kepala JavaScript paling umum yang mungkin Anda temui dan apa yang dapat Anda lakukan untuk menghindari/memperbaikinya.

Plugin WPO #1 yang akan Merusak Plugin dan Situs Anda

Mari kita mulai dengan masalah yang menjadi sumber banyak tiket di Nelio: plugin WPO.

Saya yakin Anda telah membaca banyak artikel dan posting blog yang menguraikan pentingnya memiliki situs web ringan yang memuat dengan cepat. Maksud saya, kami juga telah menulis beberapa kali tentang itu! Di antara tip yang biasanya mereka berikan, Anda akan menemukan hal-hal seperti beralih ke penyedia hosting yang lebih baik, menggunakan plugin cache dan CDN, menjaga server dan WordPress Anda tetap mutakhir, atau (dan inilah masalah pertama) menginstal plugin WPO. Beberapa contoh yang terakhir termasuk:

  • W3 Total Cache dengan lebih dari 1 juta pemasangan
  • SiteGround Optimizer dengan lebih dari 1 juta pemasangan juga (salah satunya, omong-omong, kami gunakan di situs web kami)
  • Tweaks & Optimasi WPO WordPress oleh teman baik kami Fernando Tellado

Plugin ini berjanji untuk mempercepat situs web Anda melalui serangkaian pengoptimalan yang masuk akal yang, secara umum, “situs WordPress mana pun dapat mengambil manfaat darinya.” Pengoptimalan ini meliputi:

  • Dequeuing skrip yang tidak perlu di frontend, seperti emoji atau Dashicons
  • Halaman cache dan kueri basis data
  • Mengurangi jumlah informasi yang disertakan dalam header
  • Menggabungkan dan meminimalkan skrip JavaScript dan gaya CSS
  • Memperkecil HTML
  • Menghapus argumen kueri versi dari URL aset statis Anda
  • Menunda skrip JavaScript dan/atau memuatnya secara tidak sinkron
  • dll.

Seperti yang saya katakan, jenis pengoptimalan ini mungkin, secara umum, bermanfaat. Namun menurut pengalaman kami, semua pengoptimalan JS di situs web WordPress cenderung menghasilkan lebih banyak masalah , sehingga membuat peningkatan yang seharusnya menjadi tidak berguna. Contoh nyata yang saya lihat selama bertahun-tahun adalah:

  • Menggabungkan skrip. Semakin sedikit skrip yang harus diminta browser Anda, semakin baik. Itu sebabnya masuk akal untuk menggabungkan semua skrip menjadi satu. Namun, ini bisa menjadi masalah. Secara umum, jika skrip JavaScript mogok, eksekusinya berakhir dan kesalahan dilaporkan di konsol browser Anda. Tetapi hanya eksekusi skrip itu yang dihentikan; skrip Anda yang lain akan berjalan normal. Tetapi jika Anda menggabungkan semuanya… yah, segera setelah satu skrip gagal, skrip lain (termasuk mungkin milik Anda) tidak akan berjalan, dan pengguna Anda akan berpikir bahwa plugin Andalah yang tidak berfungsi seperti yang diharapkan.
  • Memperkecil skrip. Saya telah melihat beberapa proses minifikasi yang, percaya atau tidak, merusak ekspresi reguler dan menghasilkan skrip JS yang memiliki kesalahan sintaksis. Tentu, sudah lama sejak terakhir kali saya bertemu yang ini tapi… :-/
  • Argumen kueri. Saat Anda mengantrekan skrip di WordPress, Anda dapat melakukannya dengan nomor versinya (yang mungkin dibuat secara otomatis oleh @wordpress/scripts ). Nomor versi sangat membantu: jika Anda memperbarui plugin dan skrip Anda berubah, nomor versi baru ini akan menjamin bahwa semua pengunjung melihat URL yang berbeda dan, oleh karena itu, browser mereka akan meminta versi baru. Sayangnya, jika plugin WPO menghapus string kueri, pengunjung Anda mungkin tidak menyadari bahwa skrip telah berubah dan mereka akan menggunakan salinan cache dari skrip tersebut… yang mungkin atau mungkin tidak mengakibatkan konsekuensi yang tidak diinginkan. Berengsek!

Benar-benar bencana, bukan? Tapi tunggu sampai Anda mendengar yang berikutnya:

Menunda skrip

Di Nelio, kami telah menerapkan plugin Pengujian A/B untuk melacak pengunjung Anda dan menemukan desain dan konten mana yang mendapatkan konversi paling banyak. Seperti yang dapat Anda bayangkan, skrip pelacakan kami terlihat mirip dengan ini:

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

Artinya, ini memperlihatkan fungsi init yang harus kita panggil untuk memberi tahu skrip tentang pengujian yang sedang berjalan. Untuk memanggil metode ini, kami membuat skrip inline di PHP sebagai berikut:

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

yang menghasilkan tag HTML berikut:

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

Tetapi apa yang terjadi ketika plugin WPO Anda menambahkan atribut defer ke skrip kami?

 <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> ...

Nah, skrip sekarang ditangguhkan… yang berarti cuplikan sebelumnya setara dengan yang ini:

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

dan, akibatnya, nab-tracking-js tidak lagi dimuat saat seharusnya dan, oleh karena itu, skrip sebaris yang muncul setelahnya dan bergantung padanya akan gagal: nab-tracking-js-after NelioABTesting.init which , berkat arahan defer , belum tersedia. Buruk sekali!

Larutan

Solusi paling efektif sudah jelas: beri tahu pengguna Anda untuk menonaktifkan pengoptimalan skrip dan hentikan itu sehari. Bagaimanapun, manajemen ketergantungan dalam JavaScript, secara umum, sangat rumit (terutama jika kita menggunakan arahan defer dan async ), dan WordPress tidak terkecuali. Lihat saja diskusi 12 tahun tentang topik ini!

Tetapi jika itu tidak layak (dan saya tahu itu tidak mungkin), saya sarankan Anda melakukan hal yang sama seperti yang kami lakukan: singkirkan metode init dan balikkan tanggung jawab skrip reguler dan inline Anda. Yaitu, tambahkan skrip sebaris sebelum skrip biasa dan gunakan untuk mendefinisikan variabel global dengan pengaturan yang diperlukan:

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

sehingga HTML yang dihasilkan terlihat seperti ini:

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

jadi tidak masalah jika eksekusi skrip eksternal tertunda atau tidak—itu akan selalu muncul setelah skrip inline, sehingga memuaskan ketergantungan di antara mereka.

Terakhir, jika Anda ingin memastikan bahwa tidak ada yang akan mengubah pengaturan Anda, nyatakan variabel sebagai const dan bekukan nilainya dengan Object.freeze :

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

yang didukung oleh semua browser modern.

#2 Ketergantungan di WordPress yang mungkin berfungsi atau tidak…

Manajemen ketergantungan juga bisa menjadi masalah di WordPress, terutama ketika berbicara tentang skrip bawaan WordPress. Mari saya jelaskan.

Bayangkan, misalnya, kami membuat ekstensi kecil untuk Gutenberg, seperti yang kami jelaskan di sini. Kode sumber plugin kami mungkin akan memiliki beberapa pernyataan import seperti ini:

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

Ketika kode sumber JS ini ditranspilasikan, Webpack (atau alat yang Anda gunakan) akan mengemas semua dependensi dan kode sumber Anda sendiri ke dalam satu file JS. Ini adalah file yang nantinya akan Anda enqueue dari WordPress agar semuanya berjalan seperti yang Anda harapkan.

Jika Anda menggunakan @wordpress/scripts untuk membuat file seperti itu, beberapa dependensi tidak akan disertakan dalam file output, karena proses bawaan mengasumsikan paket akan tersedia dalam lingkup global. Ini berarti impor sebelumnya akan diubah menjadi sesuatu yang mirip dengan ini:

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

Untuk memastikan Anda tidak melewatkan dependensi skrip Anda, @wordpress/scripts tidak hanya akan mengubah kode JS Anda, tetapi juga akan menghasilkan file PHP dengan dependensi WordPress-nya:

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

Cukup rapi, ya? Jadi, apa masalahnya? Nah, paket WordPress ini sedang dalam pengembangan berkelanjutan dan mereka cukup sering berubah, menambahkan fitur dan peningkatan baru. Oleh karena itu, jika Anda mengembangkan plugin menggunakan WordPress versi terbaru, Anda mungkin secara tidak sengaja menggunakan fungsi atau fitur yang tersedia di versi terbaru tersebut (dan karena itu semuanya berfungsi sebagaimana mestinya) tetapi tidak di versi WordPress “lama”…

Bagaimana Anda bisa tahu?

Larutan

Saran saya di sini sangat sederhana: kembangkan plugin Anda menggunakan versi WordPress terbaru, tetapi uji rilis Anda pada versi yang lebih lama. Secara khusus, saya sarankan Anda menguji plugin Anda dengan, setidaknya, versi WordPress minimum yang seharusnya didukung oleh plugin Anda. Versi minimum yang akan Anda temukan di readme.txt plugin Anda :

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

Beralih dari satu versi WordPress ke versi lainnya semudah menjalankan perintah WP CLI berikut:

 wp core update --version=5.4 --force

# 3 Fungsi panah lebih rumit dari yang Anda kira

Akhirnya, izinkan saya membagikan salah satu masalah terbaru yang saya temui beberapa hari yang lalu dan yang membuat saya gila. Singkatnya, kami memiliki file JavaScript yang mirip dengan ini:

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

yang menginisialisasi Formulir Nelio Anda di front-end. Scriptnya cukup mudah, bukan? Ini mendefinisikan fungsi anonim yang dipanggil saat DOM siap. Fungsi ini menggunakan fungsi pembantu (panah) yang disebut initForm . Ternyata, contoh sederhana seperti itu bisa gagal! Tetapi hanya dalam beberapa keadaan tertentu (yaitu jika skrip "dioptimalkan" oleh plugin WPO menggunakan atribut defer ).

Inilah cara JS mengeksekusi skrip sebelumnya:

  1. Fungsi anonim di dalam domReady didefinisikan
  2. domReady berjalan
  3. Jika DOM belum siap (dan biasanya belum siap saat skrip dimuat), domReady tidak menjalankan fungsi panggilan balik. Sebaliknya, itu hanya melacaknya sehingga dapat memanggilnya nanti
  4. JavaScript terus mem-parsing file dan memuat fungsi initForm
  5. Setelah DOM siap, fungsi panggilan balik akhirnya dipanggil

Sekarang, bagaimana jika, pada saat kita mencapai langkah ketiga, DOM sudah siap dan, oleh karena itu, domReady memanggil fungsi anonim secara langsung? Nah, dalam hal ini, skrip akan memicu kesalahan yang tidak ditentukan, karena initForm masih belum undefined .

Faktanya, hal yang paling aneh tentang semua ini adalah bahwa kedua solusi ini setara:

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

linter JavaScript hanya akan menimbulkan kesalahan pada yang pertama, tetapi bukan yang terbaru.

Larutan

Ada dua kemungkinan solusi: Anda mendefinisikan fungsi helper menggunakan kata kunci function dan melupakan fungsi panah, atau Anda memindahkan pernyataan domReady di akhir, setelah semua fungsi helper didefinisikan:

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

Jika Anda bertanya-tanya mengapa solusi pertama bekerja jika tampaknya setara dengan yang asli yang kami miliki, ini semua tentang cara kerja pengangkatan JavaScript. Singkatnya, dalam JavaScript Anda dapat menggunakan fungsi (didefinisikan dengan function ) sebelum definisinya, tetapi Anda tidak dapat melakukan hal yang sama dengan variabel dan konstanta (dan karenanya fungsi panah).

Singkatnya

Ada banyak hal yang bisa salah dalam JavaScript. Untungnya, mereka semua memiliki solusi, terutama jika kita memperhatikan apa yang kita lakukan. Saya harap Anda belajar sesuatu yang baru hari ini dan saya percaya bahwa berkat kesalahan dan kesalahan yang telah saya buat di masa lalu, Anda akan dapat menghindari penderitaan mereka dalam daging Anda sendiri di masa depan.

Gambar unggulan oleh Ian Stauffer di Unsplash.