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 รันสคริปต์ก่อนหน้า:

  1. มีการกำหนดฟังก์ชันที่ไม่ระบุชื่อภายใน domReady ไว้
  2. domReady วิ่ง
  3. หาก DOM ยังไม่พร้อม (และโดยปกติไม่พร้อมเมื่อโหลดสคริปต์) domReady จะไม่เรียกใช้ฟังก์ชันเรียกกลับ แต่เพียงติดตามเพื่อเรียกมันในภายหลัง
  4. JavaScript ยังคงแยกวิเคราะห์ไฟล์และโหลดฟังก์ชัน initForm
  5. เมื่อ 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