JavaScript บน WordPress คือนรก… และนี่คือเหตุผล
เผยแพร่แล้ว: 2022-05-05กองการพัฒนา WordPress มีการเปลี่ยนแปลงอย่างมากในช่วงไม่กี่ปีที่ผ่านมา นับตั้งแต่การมาถึงของ Gutenberg บทบาทของ JavaScript ใน CMS ที่เราโปรดปรานก็มีความสำคัญมากกว่าที่เคย ในบล็อกนี้ เราได้พูดคุยกันอย่างยาวเหยียดเกี่ยวกับข้อดีของสิ่งนี้สำหรับนักพัฒนา (เพื่อยกตัวอย่างบางส่วน เราได้พูดคุยเกี่ยวกับส่วนขยายของ Gutenberg คำแนะนำเกี่ยวกับ TypeScript และ React เครื่องมือในการพัฒนา ตัวอย่างปลั๊กอิน และอื่นๆ) แต่ทุก เรื่องราวมีด้าน มืด … และนั่นคือสิ่งที่เราจะพูดถึงในวันนี้
ในโพสต์ของวันนี้ ฉันจะแบ่งปัน ปัญหาหลัก 3 ประการ ที่นักพัฒนาปลั๊กอิน WordPress อาจเผชิญในอนาคต และที่ตลกก็คือ แต่ละคนก็มีผู้กระทำผิดที่แตกต่างกัน ไม่ว่าจะเป็น WordPress เอง นักพัฒนาคนอื่นๆ หรือตัวคุณเอง ต่อไป: ปวดหัว JavaScript ที่พบบ่อยที่สุดที่คุณอาจพบและสิ่งที่คุณสามารถทำได้เพื่อหลีกเลี่ยง/แก้ไข
#1 WPO Plugins ที่จะทำลาย Plugin และเว็บไซต์ของคุณ
มาเริ่มกันที่ปัญหาที่เป็นต้นเหตุของตั๋วมากมายที่ Nelio: ปลั๊กอิน WPO
ฉันแน่ใจว่าคุณได้อ่านบทความและบล็อกโพสต์มากมายที่เน้นย้ำถึงความสำคัญของการมีเว็บไซต์ขนาดเล็กที่โหลดได้รวดเร็ว ฉันหมายถึง เราได้เขียนเกี่ยวกับเรื่องนี้หลายครั้งแล้ว! ในบรรดาเคล็ดลับที่พวกเขามักจะให้ คุณจะพบกับสิ่งต่างๆ เช่น การเปลี่ยนไปใช้ผู้ให้บริการโฮสติ้งที่ดีกว่า การใช้แคชปลั๊กอินและ CDN ทำให้เซิร์ฟเวอร์และ WordPress ของคุณทันสมัยอยู่เสมอ หรือ (และนี่คือปัญหาแรก) ติดตั้งปลั๊กอิน WPO ตัวอย่างบางส่วน ได้แก่ :
- W3 Total Cache ที่มีการติดตั้งมากกว่า 1 ล้านครั้ง
- SiteGround Optimizer ที่มีการติดตั้งมากกว่า 1 ล้านครั้งด้วย (หนึ่งในนั้นคือเราใช้ในเว็บไซต์ของเรา)
- WordPress WPO Tweaks & Optimizations โดยเพื่อนที่ดีของเรา Fernando Tellado
ปลั๊กอินเหล่านี้สัญญาว่าจะเร่งความเร็วเว็บไซต์ของคุณผ่านการเพิ่มประสิทธิภาพที่เหมาะสมซึ่งโดยทั่วไปแล้ว "ไซต์ WordPress ใด ๆ จะได้รับประโยชน์จาก" การเพิ่มประสิทธิภาพเหล่านี้รวมถึง:
- การจัดคิวสคริปต์ที่ไม่จำเป็นในส่วนหน้า เช่น อิโมจิหรือ Dashicons
- แคชเพจและแบบสอบถามฐานข้อมูล
- การลดปริมาณข้อมูลที่รวมอยู่ในส่วนหัว
- การรวมและย่อสคริปต์ JavaScript และสไตล์ CSS
- ย่อ HTML
- การลบการสืบค้นเวอร์ชัน arg ออกจาก URL ของทรัพย์สินแบบคงที่ของคุณ
- เลื่อนเวลาสคริปต์ JavaScript และ/หรือโหลดสคริปต์แบบอะซิงโครนัส
- ฯลฯ
อย่างที่ฉันพูดไป การเพิ่มประสิทธิภาพประเภทนี้โดยทั่วไปอาจมีประโยชน์ แต่จากประสบการณ์ของเรา การเพิ่มประสิทธิภาพ JS ทั้งหมด บนเว็บไซต์ WordPress มักจะส่งผลให้ เกิดปัญหา มากขึ้น ดังนั้นจึงทำให้การปรับปรุงตามที่คาดคะเนไม่มีประโยชน์ ตัวอย่างจริงที่ฉันได้เห็นในช่วงหลายปีที่ผ่านมาคือ:
- การรวมสคริปต์ ยิ่งสคริปต์ของเบราว์เซอร์ของคุณต้องขอน้อยเท่าไหร่ก็ยิ่งดีเท่านั้น นั่นเป็นเหตุผลที่เหมาะสมที่จะรวมสคริปต์ทั้งหมดเป็นหนึ่งเดียว อย่างไรก็ตาม นี่อาจเป็นปัญหาได้ โดยทั่วไป หากสคริปต์ JavaScript ขัดข้อง การดำเนินการจะสิ้นสุดลงและมีการรายงานข้อผิดพลาดในคอนโซลของเบราว์เซอร์ แต่เฉพาะการดำเนินการของสคริปต์นั้นเท่านั้นที่หยุด สคริปต์อื่นๆ ของคุณจะทำงานตามปกติ แต่ถ้าคุณรวมมันทั้งหมดเข้าด้วยกัน… ทันทีที่สคริปต์หนึ่งล้มเหลว สคริปต์อื่น (รวมถึงสคริปต์ของคุณอาจเป็นของคุณ) จะไม่ทำงาน และผู้ใช้ของคุณจะคิดว่ามันเป็นปลั๊กอินของคุณที่ไม่ได้ทำงานตามที่คาดไว้
- สคริปต์ย่อขนาด ฉันเคยเห็นกระบวนการย่อขนาดบางอย่างที่เชื่อหรือไม่ว่าทำให้นิพจน์ทั่วไปเสียหายและส่งผลให้สคริปต์ JS มีข้อผิดพลาดทางไวยากรณ์ แน่นอน ครั้งสุดท้ายที่ฉันเจออันนี้ก็นานมากแล้ว แต่… :-/
- อาร์กิวเมนต์แบบสอบถาม เมื่อคุณจัดคิวสคริปต์ใน WordPress คุณสามารถทำได้โดยใช้หมายเลขเวอร์ชัน (ซึ่งอาจจะสร้างขึ้นโดยอัตโนมัติโดย
@wordpress/scripts
) หมายเลขเวอร์ชันมีประโยชน์อย่างยิ่ง: หากคุณอัปเดตปลั๊กอินและสคริปต์ของคุณมีการเปลี่ยนแปลง หมายเลขเวอร์ชันใหม่นี้จะรับประกันว่าผู้เยี่ยมชมทั้งหมดจะเห็น URL ที่แตกต่างกัน ดังนั้นเบราว์เซอร์ของพวกเขาจะขอเวอร์ชันใหม่ ขออภัย หากปลั๊กอิน WPO ลบสตริงการสืบค้น ผู้เยี่ยมชมของคุณอาจไม่ทราบว่าสคริปต์มีการเปลี่ยนแปลงและพวกเขาจะใช้สำเนาแคชของสคริปต์ดังกล่าว… ซึ่งอาจหรือไม่ก็ได้ผลลัพธ์ที่ไม่ได้ตั้งใจ เวร!
ภัยพิบัติที่สมบูรณ์ใช่มั้ย? แต่รอจนกว่าคุณจะได้ยินสิ่งต่อไป:
สคริปต์เลื่อนเวลา
ที่ Nelio เราได้ติดตั้งปลั๊กอินการทดสอบ A/B เพื่อติดตามผู้เยี่ยมชมของคุณและค้นพบว่าการออกแบบและเนื้อหาใดได้รับการแปลงมากที่สุด อย่างที่คุณสามารถจินตนาการได้ สคริปต์ติดตามของเรามีลักษณะดังนี้:
window.NelioABTesting = window.NelioABTesting || {}; window.NelioABTesting.init = ( settings ) => { // Add event listeners to track visitor events... console.log( settings ); };
นั่นคือมันแสดงฟังก์ชั่น init
ที่เราต้องเรียกใช้เพื่อให้สคริปต์ทราบเกี่ยวกับการทดสอบที่กำลังทำงานอยู่ ในการเรียกวิธีนี้ เราจัดคิวสคริปต์อินไลน์ใน PHP ดังนี้:
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' );
ซึ่งส่งผลให้แท็ก HTML ต่อไปนี้:
<head> ... <script type="text/javascript" src="https://.../dist/tracking.js" ></script> <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ...
แต่จะเกิดอะไรขึ้นเมื่อปลั๊กอิน WPO ของคุณเพิ่มแอตทริบิวต์ defer
ให้กับสคริปต์ของเรา
<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> ...
ตอนนี้สคริปต์ถูกเลื่อนออกไป… ซึ่งหมายความว่าตัวอย่างก่อนหน้านี้เทียบเท่ากับตัวอย่างนี้:
<head> ... <script type="text/javascript" > NelioABTesting.init( {"experiments":[...],...} ); </script> ... </head> <body> ... <script type="text/javascript" src="https://.../dist/tracking.js" ></script> </body> </html>
และด้วยเหตุนี้ nab-tracking-js
จึงไม่ถูกโหลดอีกต่อไปเมื่อมันควรจะเป็น ดังนั้น สคริปต์อินไลน์ที่มาหลังจากนั้นและอาศัยมันจะล้มเหลว: nab-tracking-js-after
ใช้ NelioABTesting.init
ซึ่ง ต้องขอบคุณคำสั่ง defer
ยังไม่สามารถใช้ได้ แย่มาก!
สารละลาย
วิธีแก้ปัญหาที่มีประสิทธิภาพสูงสุดนั้นชัดเจน: บอกผู้ใช้ของคุณให้ปิดการใช้งานการเพิ่มประสิทธิภาพสคริปต์และเรียกมันว่าวันนี้ ท้ายที่สุดแล้ว การจัดการการพึ่งพาใน JavaScript นั้นโดยทั่วไปแล้วซับซ้อนมาก (โดยเฉพาะอย่างยิ่งถ้าเราใช้คำสั่ง defer
และ async
) และ WordPress ก็ไม่มีข้อยกเว้น ลองดูที่การอภิปรายอายุ 12 ปีในหัวข้อนี้!
แต่ถ้านั่นเป็นไปไม่ได้ (และฉันรู้ว่าไม่ใช่) ฉันแนะนำให้คุณทำแบบเดียวกับที่เราทำ: กำจัดเมธอด init
และย้อนความรับผิดชอบของสคริปต์ปกติและอินไลน์ของคุณ กล่าวคือ เพิ่มสคริปต์อินไลน์ ก่อน สคริปต์ปกติและใช้เพื่อกำหนดตัวแปรส่วนกลางด้วยการตั้งค่าที่ต้องการ:
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' );
เพื่อให้ HTML ที่เป็นผลลัพธ์มีลักษณะดังนี้:
<head> ... <script type="text/javascript" > NelioABTestingSettings = {"experiments":[...],...}; </script> <script type="text/javascript" src="https://.../dist/tracking.js" ></script> ... </head> <body> ...
ดังนั้นจึงไม่สำคัญว่าการดำเนินการของสคริปต์ภายนอกจะล่าช้าหรือไม่—มันจะปรากฏขึ้นหลังสคริปต์อินไลน์เสมอ ดังนั้นจึงเป็นที่พอใจต่อการพึ่งพาระหว่างกัน
สุดท้าย หากคุณต้องการแน่ใจว่าจะไม่มีใครแก้ไขการตั้งค่าของคุณ ให้ประกาศตัวแปรเป็น const
และตรึงค่าของมันด้วย Object.freeze
:
... sprintf( 'const NelioABTestingSettings = Object.freeze( %s );', wp_json_encode( nab_get_tracking_settings() ) ), ...
ซึ่งได้รับการสนับสนุนโดยเบราว์เซอร์ที่ทันสมัยทั้งหมด
#2 การพึ่งพาใน WordPress ที่อาจใช้หรือไม่ได้ผล...
การจัดการการพึ่งพาอาจเป็นปัญหาใน WordPress โดยเฉพาะอย่างยิ่งเมื่อพูดถึงสคริปต์ในตัวของ WordPress ให้ฉันอธิบาย
ตัวอย่างเช่น ลองนึกภาพว่าเรากำลังสร้างส่วนขยายเล็กๆ สำหรับ Gutenberg ตามที่เราอธิบายไว้ที่นี่ ซอร์สโค้ดของปลั๊กอินของเราอาจมีคำสั่ง import
บางอย่างดังนี้:
import { RichTextToolbarButton } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { registerFormatType } from '@wordpress/rich-text'; // ...
เมื่อซอร์สโค้ด JS นี้ถูก transpiled Webpack (หรือเครื่องมือที่คุณใช้) จะจัดแพ็กเกจการพึ่งพาทั้งหมดและซอร์สโค้ดของคุณเองเป็นไฟล์ JS ไฟล์เดียว นี่คือไฟล์ที่คุณจะเข้าคิวในภายหลังจาก WordPress เพื่อให้ทุกอย่างทำงานได้ตามที่คุณคาดหวัง
หากคุณใช้ @wordpress/scripts
เพื่อสร้างไฟล์ การขึ้นต่อกันบางส่วนจะไม่รวมอยู่ในไฟล์เอาต์พุต เนื่องจากกระบวนการในตัวจะถือว่าแพ็กเกจจะพร้อมใช้งานในขอบเขตส่วนกลาง ซึ่งหมายความว่าการนำเข้าครั้งก่อนจะถูก transpiled เป็นสิ่งที่คล้ายกับสิ่งนี้:
const { RichTextToolbarButton } = window.wp.blockEditor; const { __ } = window.wp.i18n; const { registerFormatType } = window.wp.richText; // ...
เพื่อให้แน่ใจว่าคุณจะไม่พลาดการขึ้นต่อกันของสคริปต์ของคุณ @wordpress/scripts
ไม่เพียงแต่จะทรานสไพล์โค้ด JS ของคุณ แต่ยังสร้างไฟล์ PHP ที่มีการขึ้นต่อกันของ WordPress ด้วย:
<?php return array( 'dependencies' => array('wp-block-editor','wp-i18n','wp-rich-text'), 'version' => 'a12850ccaf6588b1e10968124fa4aba3', );
ค่อนข้างเรียบร้อยใช่มั้ย แล้วประเด็นคืออะไร? แพ็คเกจ WordPress เหล่านี้อยู่ภายใต้การพัฒนาอย่างต่อเนื่องและมีการเปลี่ยนแปลงค่อนข้างบ่อย โดยเพิ่มคุณสมบัติและการปรับปรุงใหม่ๆ ดังนั้น หากคุณพัฒนาปลั๊กอินโดยใช้ WordPress เวอร์ชันล่าสุด คุณอาจใช้ฟังก์ชันหรือคุณลักษณะที่มีอยู่ในเวอร์ชันล่าสุดโดยไม่ได้ตั้งใจ (และทุกอย่างทำงานได้ตามปกติ) แต่ไม่ใช่ในเวอร์ชัน "เก่ากว่า" ของ WordPress...
คุณจะบอกได้อย่างไร?
สารละลาย
คำแนะนำของฉันที่นี่ง่ายมาก: พัฒนาปลั๊กอินของคุณโดยใช้ WordPress เวอร์ชันล่าสุด แต่ทดสอบเวอร์ชันเก่าของคุณ โดยเฉพาะอย่างยิ่ง เราขอแนะนำให้คุณทดสอบปลั๊กอินด้วย อย่างน้อย เวอร์ชัน WordPress ขั้นต่ำที่ปลั๊กอินของคุณควรจะสนับสนุน เวอร์ชันขั้นต่ำที่คุณจะพบใน readme.txt
ของปลั๊กอิน:
=== Nelio Content === ... Requires PHP: 7.0 Requires at least: 5.4 Tested up to: 5.9 ...
การเปลี่ยนจาก WordPress เวอร์ชันหนึ่งเป็นเวอร์ชันอื่นทำได้ง่ายเพียงแค่รันคำสั่ง WP CLI ต่อไปนี้:
wp core update --version=5.4 --force
#3 ฟังก์ชั่นลูกศรนั้นซับซ้อนกว่าที่คุณคิด
สุดท้ายนี้ ให้ฉันแบ่งปันหนึ่งในปัญหาล่าสุดที่ฉันพบเมื่อสองสามวันก่อนและที่ทำให้ฉันแทบคลั่ง โดยสรุป เรามีไฟล์ JavaScript ที่คล้ายกับไฟล์นี้:
import domReady from '@wordpress/dom-ready'; domReady( () => [ ...document.querySelectorAll( '.nelio-forms-form' ) ] .forEach( initForm ) ); // Helpers // ------- const initForm = ( form ) => { ... } // ...
ซึ่งเริ่มต้น Nelio Forms ของคุณที่ส่วนหน้า สคริปต์ค่อนข้างตรงไปตรงมาใช่ไหม มันกำหนดฟังก์ชันที่ไม่ระบุชื่อที่เรียกเมื่อ DOM พร้อม ฟังก์ชันนี้ใช้ฟังก์ชันตัวช่วย (ลูกศร) ที่เรียกว่า initForm
ตัวอย่างง่ายๆ เช่นนั้นอาจพังได้! แต่ภายใต้สถานการณ์เฉพาะบางอย่างเท่านั้น (เช่น หากสคริปต์ "ปรับให้เหมาะสม" โดยปลั๊กอิน WPO โดยใช้แอตทริบิวต์การ defer
)
นี่คือวิธีที่ JS รันสคริปต์ก่อนหน้า:
- มีการกำหนดฟังก์ชันที่ไม่ระบุชื่อภายใน
domReady
ไว้ -
domReady
วิ่ง - หาก DOM ยังไม่พร้อม (และโดยปกติไม่พร้อมเมื่อโหลดสคริปต์)
domReady
จะไม่เรียกใช้ฟังก์ชันเรียกกลับ แต่เพียงติดตามเพื่อเรียกมันในภายหลัง - JavaScript ยังคงแยกวิเคราะห์ไฟล์และโหลดฟังก์ชัน
initForm
- เมื่อ DOM พร้อมแล้ว ฟังก์ชันการโทรกลับจะถูกเรียกในที่สุด
ทีนี้ จะเกิดอะไรขึ้นถ้าเมื่อถึงขั้นตอนที่สาม DOM ก็พร้อม ดังนั้น domReady
เรียกใช้ฟังก์ชันที่ไม่ระบุตัวตนโดยตรง ในกรณีนั้น สคริปต์จะทริกเกอร์ข้อผิดพลาดที่ไม่ได้กำหนด เนื่องจาก initForm
ยังคง undefined
อันที่จริง สิ่งที่น่าสงสัยที่สุดเกี่ยวกับเรื่องทั้งหมดนี้ก็คือว่าทั้งสองวิธีเทียบเท่ากัน:
domReady( aux ); const aux = () => {};
domReady( () => aux() ); const aux = () => {}
JavaScript linter จะแสดงข้อผิดพลาดในอันแรกเท่านั้น แต่จะไม่ใช่ข้อผิดพลาดล่าสุด
สารละลาย
มีวิธีแก้ไขที่เป็นไปได้สองวิธี: คุณกำหนดฟังก์ชันตัวช่วยโดยใช้คีย์เวิร์ดของ function
และลืมฟังก์ชันลูกศร หรือคุณย้ายคำสั่ง domReady
ต่อท้ายหลังจากที่กำหนดฟังก์ชันตัวช่วยทั้งหมดแล้ว:
domReady( aux ); function aux() { // ... }
const aux = () => { // ... }; domReady( aux );
หากคุณสงสัยว่าเหตุใดโซลูชันแรกจึงใช้งานได้ หากเห็นได้ชัดว่าเทียบเท่ากับโซลูชันดั้งเดิมที่เรามี ทั้งหมดเกี่ยวกับวิธีการทำงานของ JavaScript hoisting กล่าวโดยย่อ ใน JavaScript คุณสามารถใช้ฟังก์ชัน (กำหนดด้วย function
) ก่อนคำจำกัดความได้ แต่คุณไม่สามารถทำเช่นเดียวกันกับตัวแปรและค่าคงที่ (และด้วยเหตุนี้ฟังก์ชันลูกศร)
สรุป
มีหลายสิ่งหลายอย่างที่อาจผิดพลาดได้ใน JavaScript โชคดีที่พวกเขาทั้งหมดมีวิธีแก้ปัญหา โดยเฉพาะอย่างยิ่งถ้าเราใส่ใจกับสิ่งที่เราทำ ฉันหวังว่าคุณจะได้เรียนรู้สิ่งใหม่ในวันนี้ และฉันเชื่อว่าต้องขอบคุณความผิดพลาดและความผิดพลาดที่ฉันได้ทำไว้ในอดีต คุณจะสามารถหลีกเลี่ยงความทุกข์ทรมานเหล่านี้ในเนื้อหนังของคุณเองในอนาคต
ภาพเด่นโดย Ian Stauffer บน Unsplash