หลีกเลี่ยงหลุมพรางของโค้ดแบบอินไลน์อัตโนมัติ

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ การใช้โค้ด CSS หรือ JS แบบอินไลน์มากเกินไป แทนที่จะแสดงโค้ดผ่านทรัพยากรแบบคงที่ อาจส่งผลเสียต่อประสิทธิภาพของไซต์ ในบทความนี้ เราจะเรียนรู้วิธีโหลดโค้ดไดนามิกผ่านไฟล์สแตติกแทน เพื่อหลีกเลี่ยงข้อเสียของโค้ดอินไลน์ที่มากเกินไป

Inlining เป็นกระบวนการของการรวมเนื้อหาของไฟล์โดยตรงในเอกสาร HTML: ไฟล์ CSS สามารถ inline ภายในองค์ประกอบ style และไฟล์ JavaScript สามารถ inline ภายในองค์ประกอบ script :

 <style> /* CSS contents here */ </style> <script> /* JS contents here */ </script>

โดยการพิมพ์โค้ดที่อยู่ในเอาต์พุต HTML แล้ว การอินไลน์จะช่วยหลีกเลี่ยงคำขอบล็อกการแสดงผลและดำเนินการโค้ดก่อนที่จะแสดงผลหน้าเว็บ ด้วยเหตุนี้ จึงเป็นประโยชน์ในการปรับปรุงประสิทธิภาพการรับรู้ของไซต์ (เช่น เวลาที่ใช้เพื่อให้หน้าเว็บใช้งานได้) ตัวอย่างเช่น เราสามารถใช้บัฟเฟอร์ของข้อมูลที่ส่งได้ทันทีเมื่อโหลดไซต์ (ประมาณ 14kb) ไปยังอินไลน์ สไตล์ที่สำคัญ รวมถึงสไตล์ของเนื้อหาครึ่งหน้าบน (อย่างที่เคยทำในเว็บไซต์ Smashing Magazine ครั้งก่อน) และขนาดตัวอักษร ความกว้างและความสูงของเลย์เอาต์ เพื่อหลีกเลี่ยงไม่ให้เลย์เอาต์แสดงผลซ้ำๆ เมื่อส่งข้อมูลที่เหลือ .

อย่างไรก็ตาม เมื่อใช้งานมากเกินไป โค้ดในบรรทัดอาจมีผลเสียต่อประสิทธิภาพของไซต์: เนื่องจากโค้ดไม่สามารถแคชได้ เนื้อหาเดียวกันจะถูกส่งไปยังไคลเอ็นต์ซ้ำแล้วซ้ำอีก และไม่สามารถแคชล่วงหน้าผ่าน Service Workers หรือ แคชและเข้าถึงได้จากเครือข่ายการจัดส่งเนื้อหา นอกจากนี้ สคริปต์อินไลน์ถือว่าไม่ปลอดภัยเมื่อใช้นโยบายความปลอดภัยเนื้อหา (CSP) จากนั้นจึงสร้างกลยุทธ์ที่สมเหตุสมผลในการอินไลน์ส่วนสำคัญของ CSS และ JS ที่ทำให้ไซต์โหลดเร็วขึ้น แต่หลีกเลี่ยงให้มากที่สุดเท่าที่จะทำได้

โดยมีวัตถุประสงค์ในการหลีกเลี่ยง inlining ในบทความนี้ เราจะศึกษาวิธีการแปลงโค้ดอินไลน์เป็นสแตติกแอสเซ็ท: แทนที่จะพิมพ์โค้ดในเอาต์พุต HTML เราจะบันทึกลงในดิสก์ (สร้างไฟล์สแตติกอย่างมีประสิทธิภาพ) และเพิ่ม <script> ที่เกี่ยวข้อง <script> หรือ <link> เพื่อโหลดไฟล์

มาเริ่มกันเลย!

การอ่านที่แนะนำ : WordPress Security As A Process

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

เมื่อใดควรหลีกเลี่ยง Inlining

ไม่มีสูตรมหัศจรรย์ที่จะกำหนดได้ว่าโค้ดบางโค้ดต้องอยู่ในบรรทัดหรือไม่ อย่างไรก็ตาม สามารถเห็นได้อย่างชัดเจนเมื่อโค้ดบางโค้ดต้องไม่อินไลน์: เมื่อมีโค้ดจำนวนมาก และเมื่อไม่จำเป็นต้องใช้ในทันที

ตัวอย่างเช่น ไซต์ WordPress ในบรรทัดเทมเพลต JavaScript เพื่อแสดง Media Manager (เข้าถึงได้ในหน้า Media Library ภายใต้ /wp-admin/upload.php ) การพิมพ์โค้ดจำนวนมาก:

สกรีนช็อตของซอร์สโค้ดสำหรับหน้าไลบรารีสื่อ
เทมเพลต JavaScript ที่รวมอยู่ใน WordPress Media Manager

ด้วยขนาดเต็ม 43kb ขนาดของโค้ดนี้จึงไม่สำคัญ และเนื่องจากโค้ดนี้อยู่ที่ด้านล่างของหน้าจึงไม่จำเป็นในทันที ดังนั้นจึงควรแสดงโค้ดนี้ผ่านเนื้อหาสแตติกแทนหรือพิมพ์โค้ดนี้ในเอาต์พุต HTML

มาดูวิธีการแปลงโค้ดอินไลน์เป็นสแตติกแอสเซ็ทกันต่อไป

ทริกเกอร์การสร้างไฟล์คงที่

หากเนื้อหา (ที่จะถูกแทรกในบรรทัด) มาจากไฟล์สแตติก ก็ไม่ต้องทำอะไรมากนอกจากขอไฟล์สแตติกนั้นแทนการอินไลน์โค้ด

สำหรับโค้ดไดนามิก เราต้องวางแผนว่าจะสร้างไฟล์สแตติกพร้อมกับเนื้อหาอย่างไร/เมื่อใด ตัวอย่างเช่น หากไซต์มีตัวเลือกการกำหนดค่า (เช่น การเปลี่ยนรูปแบบสีหรือภาพพื้นหลัง) เมื่อใดควรสร้างไฟล์ที่มีค่าใหม่ เรามีโอกาสสร้างไฟล์สแตติกจากโค้ดไดนามิกดังต่อไปนี้:

  1. ตามคำขอร้อง
    เมื่อผู้ใช้เข้าถึงเนื้อหาเป็นครั้งแรก
  2. เมื่อมีการเปลี่ยนแปลง
    เมื่อซอร์สโค้ดไดนามิก (เช่น ค่าคอนฟิกูเรชัน) มีการเปลี่ยนแปลง

พิจารณาตามคำขอก่อน ครั้งแรกที่ผู้ใช้เข้าถึงไซต์ สมมติว่าผ่าน /index.html ไฟล์สแตติก (เช่น header-colors.css ) ยังไม่มีอยู่ ดังนั้นจึงต้องสร้างขึ้น ลำดับเหตุการณ์มีดังต่อไปนี้:

  1. ผู้ใช้ร้องขอ /index.html ;
  2. เมื่อประมวลผลคำขอ เซิร์ฟเวอร์จะตรวจสอบว่าไฟล์ header-colors.css มีอยู่หรือไม่ เนื่องจากไม่ได้รับซอร์สโค้ดและสร้างไฟล์บนดิสก์
  3. ส่งคืนการตอบกลับไปยังไคลเอนต์ รวมถึงแท็ก <link rel="stylesheet" type="text/css" href="/staticfiles/header-colors.css">
  4. เบราว์เซอร์ดึงทรัพยากรทั้งหมดที่รวมอยู่ในหน้า รวมถึง header-colors.css ;
  5. เมื่อถึงตอนนั้น ไฟล์นี้จึงมีอยู่ ดังนั้นจึงให้บริการ

อย่างไรก็ตาม ลำดับของเหตุการณ์ก็อาจแตกต่างกัน นำไปสู่ผลลัพธ์ที่ไม่น่าพอใจ ตัวอย่างเช่น:

  1. ผู้ใช้ร้องขอ /index.html ;
  2. ไฟล์นี้ถูกแคชไว้โดยเบราว์เซอร์ (หรือพร็อกซีอื่นๆ หรือผ่าน Service Workers) ดังนั้นคำขอจะไม่ถูกส่งไปยังเซิร์ฟเวอร์
  3. เบราว์เซอร์ดึงทรัพยากรทั้งหมดที่รวมอยู่ในหน้า รวมถึง header-colors.css อย่างไรก็ตาม อิมเมจนี้ไม่ได้ถูกแคชในเบราว์เซอร์ ดังนั้นคำขอจะถูกส่งไปยังเซิร์ฟเวอร์
  4. เซิร์ฟเวอร์ยังไม่ได้สร้าง header-colors.css (เช่น เพิ่งรีสตาร์ท)
  5. มันจะส่งคืน 404

หรืออีกทางหนึ่ง เราสามารถสร้าง header-colors.css ไม่ใช่เมื่อร้องขอ /index.html แต่เมื่อร้องขอ /header-colors.css เอง อย่างไรก็ตาม เนื่องจากในตอนแรกไฟล์นี้ไม่มีอยู่ คำขอจึงได้รับการปฏิบัติเป็น 404 แม้ว่าเราจะสามารถแฮ็กข้อมูลได้ เปลี่ยนส่วนหัวเพื่อเปลี่ยนรหัสสถานะเป็น 200 และส่งคืนเนื้อหาของรูปภาพ นี่เป็นวิธีการทำสิ่งต่าง ๆ ที่แย่มาก ดังนั้นเราจะไม่สนุกกับความเป็นไปได้นี้ (เราดีกว่านี้มาก!)

เหลือตัวเลือกเดียวเท่านั้น: การสร้างไฟล์สแตติกหลังจากเปลี่ยนแหล่งที่มา

การสร้างไฟล์คงที่เมื่อแหล่งที่มาเปลี่ยนแปลง

โปรดสังเกตว่า เราสามารถสร้างโค้ดไดนามิกจากแหล่งที่มาทั้งที่ขึ้นกับผู้ใช้และที่ขึ้นกับไซต์ ตัวอย่างเช่น หากธีมอนุญาตให้เปลี่ยนรูปภาพพื้นหลังของไซต์และตัวเลือกนั้นได้รับการกำหนดค่าโดยผู้ดูแลระบบของไซต์ ไฟล์สแตติกก็สามารถสร้างเป็นส่วนหนึ่งของกระบวนการปรับใช้ได้ ในทางกลับกัน หากไซต์อนุญาตให้ผู้ใช้เปลี่ยนภาพพื้นหลังสำหรับโปรไฟล์ของพวกเขา ไฟล์สแตติกจะต้องถูกสร้างขึ้นบนรันไทม์

สรุปเรามีสองกรณีนี้:

  1. การกำหนดค่าผู้ใช้
    กระบวนการต้องถูกทริกเกอร์เมื่อผู้ใช้อัปเดตการกำหนดค่า
  2. การกำหนดค่าไซต์
    กระบวนการต้องถูกทริกเกอร์เมื่อผู้ดูแลระบบอัปเดตการกำหนดค่าสำหรับไซต์ หรือก่อนที่จะปรับใช้ไซต์

หากเราพิจารณาทั้งสองกรณีแยกกัน สำหรับ #2 เราสามารถออกแบบกระบวนการบนสแต็กเทคโนโลยีใดก็ได้ที่เราต้องการ อย่างไรก็ตาม เราไม่ต้องการใช้โซลูชันที่แตกต่างกันสองวิธี แต่เป็นโซลูชันที่ไม่เหมือนใครซึ่งสามารถจัดการกับทั้งสองกรณีได้ และเนื่องจากตั้งแต่ #1 กระบวนการในการสร้างไฟล์สแตติกจะต้องถูกทริกเกอร์บนไซต์ที่ทำงานอยู่ ดังนั้นจึงเป็นเรื่องที่น่าสนใจที่จะออกแบบกระบวนการนี้โดยใช้เทคโนโลยีเดียวกันกับสแต็คที่ไซต์ทำงานอยู่

เมื่อออกแบบกระบวนการ โค้ดของเราจะต้องจัดการกับสถานการณ์เฉพาะของทั้ง #1 และ #2:

  • การกำหนดเวอร์ชัน
    ไฟล์สแตติกต้องเข้าถึงได้ด้วยพารามิเตอร์ "เวอร์ชัน" เพื่อทำให้ไฟล์ก่อนหน้าเป็นโมฆะเมื่อมีการสร้างไฟล์สแตติกใหม่ แม้ว่า #2 อาจมีการกำหนดเวอร์ชันเดียวกันกับไซต์ แต่ #1 ต้องใช้เวอร์ชันไดนามิกสำหรับผู้ใช้แต่ละราย ซึ่งอาจบันทึกไว้ในฐานข้อมูล
  • ตำแหน่งของไฟล์ที่สร้างขึ้น
    #2 สร้างไฟล์สแตติกที่ไม่ซ้ำกันสำหรับทั้งไซต์ (เช่น /staticfiles/header-colors.css ) ในขณะที่ #1 สร้างไฟล์สแตติกสำหรับผู้ใช้แต่ละคน (เช่น /staticfiles/users/leo/header-colors.css )
  • เหตุการณ์ทริกเกอร์
    ในขณะที่ #1 ไฟล์สแตติกต้องถูกเรียกใช้งานบนรันไทม์ สำหรับ #2 ไฟล์นั้นสามารถถูกเรียกใช้งานโดยเป็นส่วนหนึ่งของกระบวนการบิลด์ในสภาพแวดล้อมการจัดเตรียมของเรา
  • การปรับใช้และการจัดจำหน่าย
    ไฟล์สแตติกใน #2 สามารถรวมเข้าด้วยกันอย่างราบรื่นภายในบันเดิลการปรับใช้ของไซต์ โดยไม่มีความท้าทาย อย่างไรก็ตาม ไฟล์สแตติกใน #1 ทำไม่ได้ ดังนั้นกระบวนการจึงต้องจัดการกับข้อกังวลเพิ่มเติม เช่น เซิร์ฟเวอร์หลายตัวที่อยู่เบื้องหลังโหลดบาลานเซอร์ (ไฟล์สแตติกจะถูกสร้างขึ้นใน 1 เซิร์ฟเวอร์เท่านั้นหรือทั้งหมด และอย่างไร)

มาออกแบบและดำเนินการตามกระบวนการต่อไป ในการสร้างไฟล์สแตติกแต่ละไฟล์ เราต้องสร้างอ็อบเจ็กต์ที่มีข้อมูลเมตาของไฟล์ คำนวณเนื้อหาจากแหล่งที่มาแบบไดนามิก และสุดท้ายบันทึกไฟล์สแตติกลงในดิสก์ เป็นกรณีการใช้งานเพื่อเป็นแนวทางในการอธิบายด้านล่าง เราจะสร้างไฟล์สแตติกต่อไปนี้:

  1. header-colors.css พร้อมสไตล์บางส่วนจากค่าที่บันทึกไว้ในฐานข้อมูล
  2. welcomeuser-data.js มีวัตถุ JSON ที่มีข้อมูลผู้ใช้ภายใต้ตัวแปรบางตัว: window.welcomeUserData = {name: "Leo"}; .

ด้านล่างนี้ ฉันจะอธิบายขั้นตอนการสร้างไฟล์สแตติกสำหรับ WordPress ซึ่งเราต้องใช้สแต็คบนฟังก์ชัน PHP และ WordPress ฟังก์ชันในการสร้างไฟล์สแตติกก่อนการปรับใช้สามารถทริกเกอร์ได้โดยการโหลดหน้าพิเศษที่ใช้ [create_static_files] ตามที่ผมได้อธิบายไว้ในบทความก่อนหน้านี้

บทแนะนำเพิ่มเติม : Making A Service Worker: A Case Study

แสดงไฟล์เป็นวัตถุ

เราต้องสร้างโมเดลไฟล์เป็นอ็อบเจกต์ PHP ที่มีคุณสมบัติที่เกี่ยวข้องทั้งหมด ดังนั้น เราทั้งสองสามารถบันทึกไฟล์บนดิสก์ในตำแหน่งเฉพาะ (เช่น ภายใต้ /staticfiles/ หรือ /staticfiles/users/leo/ ) และรู้วิธีขอ ไฟล์ดังนั้น สำหรับสิ่งนี้ เราสร้างอินเทอร์เฟซ Resource ที่ส่งคืนทั้งข้อมูลเมตาของไฟล์ (ชื่อไฟล์, dir, ประเภท: “css” หรือ “js” เวอร์ชัน และการพึ่งพาทรัพยากรอื่นๆ) และเนื้อหา

 interface Resource { function get_filename(); function get_dir(); function get_type(); function get_version(); function get_dependencies(); function get_content(); }

ในการทำให้โค้ดสามารถบำรุงรักษาและนำกลับมาใช้ใหม่ได้ เราปฏิบัติตามหลักการ SOLID ซึ่งเรากำหนดรูปแบบการสืบทอดอ็อบเจ็กต์สำหรับทรัพยากรเพื่อค่อยๆ เพิ่มคุณสมบัติ โดยเริ่มจาก ResourceBase คลาสนามธรรมซึ่งการใช้งานทรัพยากรทั้งหมดของเราจะได้รับมรดก:

 abstract class ResourceBase implements Resource { function get_dependencies() { // By default, a file has no dependencies return array(); } }

ตาม SOLID เราสร้างคลาสย่อยเมื่อใดก็ตามที่คุณสมบัติต่างกัน ตามที่ระบุไว้ก่อนหน้านี้ ตำแหน่งของไฟล์สแตติกที่สร้างขึ้น และการกำหนดเวอร์ชันที่จะร้องขอจะแตกต่างกันขึ้นอยู่กับไฟล์ที่เกี่ยวกับการกำหนดค่าผู้ใช้หรือไซต์:

 abstract class UserResourceBase extends ResourceBase { function get_dir() { // A different file and folder for each user $user = wp_get_current_user(); return "/staticfiles/users/{$user->user_login}/"; } function get_version() { // Save the resource version for the user under her meta data. // When the file is regenerated, must execute `update_user_meta` to increase the version number $user_id = get_current_user_id(); $meta_key = "resource_version_".$this->get_filename(); return get_user_meta($user_id, $meta_key, true); } } abstract class SiteResourceBase extends ResourceBase { function get_dir() { // All files are placed in the same folder return "/staticfiles/"; } function get_version() { // Same versioning as the site, assumed defined under a constant return SITE_VERSION; } }

ขั้นสุดท้าย เราใช้อ็อบเจ็กต์สำหรับไฟล์ที่เราต้องการสร้าง เพิ่มชื่อไฟล์ ประเภทของไฟล์ และโค้ดไดนามิกผ่านฟังก์ชัน get_content :

 class HeaderColorsSiteResource extends SiteResourceBase { function get_filename() { return "header-colors"; } function get_type() { return "css"; } function get_content() { return sprintf( " .site-title a { color: #%s; } ", esc_attr(get_header_textcolor()) ); } } class WelcomeUserDataUserResource extends UserResourceBase { function get_filename() { return "welcomeuser-data"; } function get_type() { return "js"; } function get_content() { $user = wp_get_current_user(); return sprintf( "window.welcomeUserData = %s;", json_encode( array( "name" => $user->display_name ) ) ); } }

ด้วยเหตุนี้ เราจึงสร้างโมเดลไฟล์เป็นอ็อบเจกต์ PHP ต่อไปเราต้องบันทึกลงในดิสก์

การบันทึกไฟล์สแตติกไปยังดิสก์

การบันทึกไฟล์ลงดิสก์สามารถทำได้ง่าย ๆ ผ่านฟังก์ชันดั้งเดิมของภาษา ในกรณีของ PHP สามารถทำได้โดยใช้ฟังก์ชัน fwrite นอกจากนี้ เราสร้างคลาสยูทิลิตี้ ResourceUtils ด้วยฟังก์ชันที่ให้พาธสัมบูรณ์ไปยังไฟล์บนดิสก์ และพาธที่สัมพันธ์กับรูทของไซต์ด้วย:

 class ResourceUtils { protected static function get_file_relative_path($fileObject) { return $fileObject->get_dir().$fileObject->get_filename().".".$fileObject->get_type(); } static function get_file_path($fileObject) { // Notice that we must add constant WP_CONTENT_DIR to make the path absolute when saving the file return WP_CONTENT_DIR.self::get_file_relative_path($fileObject); } } class ResourceGenerator { static function save($fileObject) { $file_path = ResourceUtils::get_file_path($fileObject); $handle = fopen($file_path, "wb"); $numbytes = fwrite($handle, $fileObject->get_content()); fclose($handle); } }

จากนั้น เมื่อใดก็ตามที่แหล่งที่มาเปลี่ยนแปลงและจำเป็นต้องสร้างไฟล์สแตติกขึ้นใหม่ เราจะดำเนินการ ResourceGenerator::save โดยส่งผ่านอ็อบเจ็กต์ที่แสดงไฟล์เป็นพารามิเตอร์ รหัสด้านล่างสร้างใหม่และบันทึกบนดิสก์ ไฟล์ "header-colors.css" และ "welcomeuser-data.js":

 // When need to regenerate header-colors.css, execute: ResourceGenerator::save(new HeaderColorsSiteResource()); // When need to regenerate welcomeuser-data.js, execute: ResourceGenerator::save(new WelcomeUserDataUserResource());

เมื่อมีอยู่แล้ว เราสามารถจัดคิวไฟล์เพื่อโหลดผ่านแท็ก <script> และ <link>

การเข้าคิวไฟล์คงที่

การจัดคิวไฟล์สแตติกนั้นไม่แตกต่างจากการจัดคิวทรัพยากรใด ๆ ใน WordPress: ผ่านฟังก์ชั่น wp_enqueue_script และ wp_enqueue_style จากนั้น เราเพียงแค่วนซ้ำอินสแตนซ์ของอ็อบเจ็กต์ทั้งหมด และใช้เบ็ดเดียวหรืออย่างอื่นขึ้นอยู่กับค่า get_type() ของพวกเขาว่าเป็น "js" หรือ "css"

ขั้นแรก เราเพิ่มฟังก์ชันยูทิลิตี้เพื่อระบุ URL ของไฟล์ และเพื่อบอกประเภทว่าเป็น JS หรือ CSS:

 class ResourceUtils { // Continued from above... static function get_file_url($fileObject) { // Add the site URL before the file path return get_site_url().self::get_file_relative_path($fileObject); } static function is_css($fileObject) { return $fileObject->get_type() == "css"; } static function is_js($fileObject) { return $fileObject->get_type() == "js"; } }

อินสแตนซ์ของคลาส ResourceEnqueuer จะมีไฟล์ทั้งหมดที่ต้องโหลด เมื่อเรียกใช้ ฟังก์ชัน enqueue_scripts และ enqueue_styles จะทำการจัดคิวโดยเรียกใช้ฟังก์ชัน WordPress ที่เกี่ยวข้อง ( wp_enqueue_script และ wp_enqueue_style ตามลำดับ):

 class ResourceEnqueuer { protected $fileObjects; function __construct($fileObjects) { $this->fileObjects = $fileObjects; } protected function get_file_properties($fileObject) { $handle = $fileObject->get_filename(); $url = ResourceUtils::get_file_url($fileObject); $dependencies = $fileObject->get_dependencies(); $version = $fileObject->get_version(); return array($handle, $url, $dependencies, $version); } function enqueue_scripts() { $jsFileObjects = array_map(array(ResourceUtils::class, 'is_js'), $this->fileObjects); foreach ($jsFileObjects as $fileObject) { list($handle, $url, $dependencies, $version) = $this->get_file_properties($fileObject); wp_register_script($handle, $url, $dependencies, $version); wp_enqueue_script($handle); } } function enqueue_styles() { $cssFileObjects = array_map(array(ResourceUtils::class, 'is_css'), $this->fileObjects); foreach ($cssFileObjects as $fileObject) { list($handle, $url, $dependencies, $version) = $this->get_file_properties($fileObject); wp_register_style($handle, $url, $dependencies, $version); wp_enqueue_style($handle); } } }

สุดท้าย เราสร้างอ็อบเจ็กต์ของคลาส ResourceEnqueuer พร้อมรายการอ็อบเจ็กต์ PHP ที่เป็นตัวแทนของแต่ละไฟล์ และเพิ่ม WordPress hook เพื่อดำเนินการเข้าคิว:

 // Initialize with the corresponding object instances for each file to enqueue $fileEnqueuer = new ResourceEnqueuer( array( new HeaderColorsSiteResource(), new WelcomeUserDataUserResource() ) ); // Add the WordPress hooks to enqueue the resources add_action('wp_enqueue_scripts', array($fileEnqueuer, 'enqueue_scripts')); add_action('wp_print_styles', array($fileEnqueuer, 'enqueue_styles'));

แค่นั้นแหละ: เมื่อเข้าคิว ไฟล์สแตติกจะถูกร้องขอเมื่อโหลดไซต์ในไคลเอนต์ เราได้ประสบความสำเร็จในการหลีกเลี่ยงการพิมพ์โค้ดแบบอินไลน์และการโหลดทรัพยากรแบบคงที่แทน

ต่อไป เราสามารถประยุกต์ใช้การปรับปรุงหลายอย่างเพื่อประสิทธิภาพที่เพิ่มขึ้น

การอ่านที่แนะนำ : บทนำสู่การทดสอบปลั๊กอิน WordPress อัตโนมัติด้วย PHPUnit

การรวมไฟล์เข้าด้วยกัน

แม้ว่า HTTP/2 ได้ลดความจำเป็นในการรวมไฟล์ แต่ก็ยังทำให้ไซต์เร็วขึ้น เนื่องจากการบีบอัดไฟล์ (เช่น ผ่าน GZip) จะมีประสิทธิภาพมากกว่า และเนื่องจากเบราว์เซอร์ (เช่น Chrome) มีค่าใช้จ่ายที่มากกว่าในการประมวลผล ทรัพยากรจำนวนมาก .

ถึงตอนนี้ เราได้จำลองไฟล์เป็นอ็อบเจกต์ PHP ซึ่งช่วยให้เราจัดการกับอ็อบเจกต์นี้เป็นอินพุตสำหรับกระบวนการอื่นๆ โดยเฉพาะอย่างยิ่ง เราสามารถทำซ้ำขั้นตอนเดียวกันข้างต้นเพื่อรวมไฟล์ทั้งหมดจากประเภทเดียวกันเข้าด้วยกันและให้บริการเวอร์ชันที่รวมกลุ่มแทนไฟล์อิสระทั้งหมด สำหรับสิ่งนี้ เราสร้างฟังก์ชัน get_content ซึ่งแยกเนื้อหาจากทุกทรัพยากรภายใต้ $fileObjects และพิมพ์อีกครั้ง ทำให้เกิดการรวมเนื้อหาทั้งหมดจากทรัพยากรทั้งหมด:

 abstract class SiteBundleBase extends SiteResourceBase { protected $fileObjects; function __construct($fileObjects) { $this->fileObjects = $fileObjects; } function get_content() { $content = ""; foreach ($this->fileObjects as $fileObject) { $content .= $fileObject->get_content().PHP_EOL; } return $content; } }

เราสามารถรวมไฟล์ทั้งหมดเข้าด้วยกันเป็นไฟล์ bundled-styles.css โดยสร้างคลาสสำหรับไฟล์นี้:

 class StylesSiteBundle extends SiteBundleBase { function get_filename() { return "bundled-styles"; } function get_type() { return "css"; } }

สุดท้าย เราเพียงแค่จัดคิวไฟล์ที่รวมกลุ่มเหล่านี้เหมือนเมื่อก่อน แทนที่จะเป็นทรัพยากรอิสระทั้งหมด สำหรับ CSS เราสร้างบันเดิลที่มีไฟล์ header-colors.css , background-image.css และ font-sizes.css ซึ่งเราเพียงแค่ยกตัวอย่าง StylesSiteBundle ด้วยอ็อบเจกต์ PHP สำหรับแต่ละไฟล์เหล่านี้ (และเราสามารถสร้าง JS ได้เช่นเดียวกัน ไฟล์บันเดิล):

 $fileObjects = array( // CSS new HeaderColorsSiteResource(), new BackgroundImageSiteResource(), new FontSizesSiteResource(), // JS new WelcomeUserDataUserResource(), new UserShoppingItemsUserResource() ); $cssFileObjects = array_map(array(ResourceUtils::class, 'is_css'), $fileObjects); $jsFileObjects = array_map(array(ResourceUtils::class, 'is_js'), $fileObjects); // Use this definition of $fileEnqueuer instead of the previous one $fileEnqueuer = new ResourceEnqueuer( array( new StylesSiteBundle($cssFileObjects), new ScriptsSiteBundle($jsFileObjects) ) );

แค่นั้นแหละ. ตอนนี้เราจะขอไฟล์ JS เพียงไฟล์เดียวและไฟล์ CSS หนึ่งไฟล์แทนที่จะเป็นหลายไฟล์

การปรับปรุงขั้นสุดท้ายสำหรับประสิทธิภาพที่รับรู้นั้นเกี่ยวข้องกับการจัดลำดับความสำคัญของเนื้อหา โดยการชะลอการโหลดเนื้อหาเหล่านั้นซึ่งไม่จำเป็นในทันที มาจัดการเรื่องนี้กันต่อไป

async / defer Attributes สำหรับ JS Resources

เราสามารถเพิ่มแอตทริบิวต์ async และ defer ไปที่แท็ก <script> เพื่อแก้ไขเมื่อไฟล์ JavaScript ถูกดาวน์โหลด แยกวิเคราะห์ และดำเนินการ เพื่อจัดลำดับความสำคัญ JavaScript ที่สำคัญและผลักดันทุกอย่างที่ไม่สำคัญให้ช้าที่สุดเท่าที่จะเป็นไปได้ ซึ่งจะช่วยลดการโหลดที่เห็นได้ชัดของไซต์ เวลา.

ในการใช้งานคุณลักษณะนี้ ตามหลักการ SOLID เราควรสร้างอินเทอร์เฟซใหม่ JSResource (ซึ่งสืบทอดมาจาก Resource ) ที่มีฟังก์ชัน is_async และ is_defer อย่างไรก็ตาม การทำเช่นนี้จะปิดประตูสู่แท็ก <style> ที่สนับสนุนคุณลักษณะเหล่านี้ในที่สุด ดังนั้น โดยคำนึงถึงความสามารถในการปรับตัว เราจึงใช้วิธีการแบบปลายเปิดมากขึ้น: เราเพียงแค่เพิ่มวิธีการทั่วไป get_attributes ให้กับอินเทอร์เฟซ Resource เพื่อให้มีความยืดหยุ่นในการเพิ่มแอตทริบิวต์ใดๆ (ทั้งที่มีอยู่แล้วหรือยังไม่ได้ประดิษฐ์ขึ้น) สำหรับทั้งสอง <script> และ <link> แท็ก:

 interface Resource { // Continued from above... function get_attributes(); } abstract class ResourceBase implements Resource { // Continued from above... function get_attributes() { // By default, no extra attributes return ''; } }

WordPress ไม่มีวิธีง่ายๆ ในการเพิ่มแอตทริบิวต์พิเศษให้กับทรัพยากรที่เข้าคิว ดังนั้นเราจึงดำเนินการในลักษณะที่ค่อนข้างแฮ็ก โดยเพิ่มเบ็ดที่แทนที่สตริงภายในแท็กผ่านฟังก์ชัน add_script_tag_attributes :

 class ResourceEnqueuerUtils { protected static tag_attributes = array(); static function add_tag_attributes($handle, $attributes) { self::tag_attributes[$handle] = $attributes; } static function add_script_tag_attributes($tag, $handle, $src) { if ($attributes = self::tag_attributes[$handle]) { $tag = str_replace( " src='${src}'>", " src='${src}' ".$attributes.">", $tag ); } return $tag; } } // Initize by connecting to the WordPress hook add_filter( 'script_loader_tag', array(ResourceEnqueuerUtils::class, 'add_script_tag_attributes'), PHP_INT_MAX, 3 );

เราเพิ่มแอตทริบิวต์สำหรับทรัพยากรเมื่อสร้างอินสแตนซ์ของวัตถุที่เกี่ยวข้อง:

 abstract class ResourceBase implements Resource { // Continued from above... function __construct() { ResourceEnqueuerUtils::add_tag_attributes($this->get_filename(), $this->get_attributes()); } }

สุดท้าย หากไม่จำเป็นต้องดำเนินการทรัพยากร welcomeuser-data.js ทันที เราก็สามารถตั้งค่าเป็น defer ได้:

 class WelcomeUserDataUserResource extends UserResourceBase { // Continued from above... function get_attributes() { return "defer='defer'"; } }

เนื่องจากมีการโหลดแบบเลื่อนเวลา สคริปต์จะโหลดในภายหลัง นำจุดที่ผู้ใช้สามารถโต้ตอบกับไซต์ได้ เกี่ยวกับประสิทธิภาพที่เพิ่มขึ้น ตอนนี้เราพร้อมแล้ว!

มีปัญหาหนึ่งเรื่องที่ต้องแก้ไขก่อนที่เราจะผ่อนคลายได้: จะเกิดอะไรขึ้นเมื่อไซต์ถูกโฮสต์บนเซิร์ฟเวอร์หลายเครื่อง

การจัดการกับเซิร์ฟเวอร์หลายเครื่องที่อยู่เบื้องหลังโหลดบาลานเซอร์

หากไซต์ของเราโฮสต์อยู่บนไซต์หลายแห่งที่อยู่เบื้องหลังโหลดบาลานเซอร์ และไฟล์ที่ขึ้นอยู่กับการกำหนดค่าผู้ใช้ถูกสร้างขึ้นใหม่ เซิร์ฟเวอร์ที่จัดการคำขอจะต้องอัปโหลดไฟล์สแตติกที่สร้างใหม่ไปยังเซิร์ฟเวอร์อื่นทั้งหมด มิฉะนั้น เซิร์ฟเวอร์อื่นจะให้บริการไฟล์นั้นในเวอร์ชันเก่านับจากช่วงเวลานั้นเป็นต้นไป เราจะทำเช่นนี้ได้อย่างไร? การมีเซิร์ฟเวอร์สื่อสารถึงกันไม่ได้เป็นเพียงความซับซ้อน แต่ท้ายที่สุดอาจพิสูจน์ได้ว่าไม่สามารถทำได้: จะเกิดอะไรขึ้นหากไซต์ทำงานบนเซิร์ฟเวอร์หลายร้อยเครื่องจากภูมิภาคต่างๆ เห็นได้ชัดว่านี่ไม่ใช่ตัวเลือก

วิธีแก้ปัญหาที่ฉันพบคือการเพิ่มระดับของทางอ้อม: แทนที่จะขอไฟล์สแตติกจาก URL ของไซต์ พวกเขาจะได้รับการร้องขอจากตำแหน่งในระบบคลาวด์ เช่น จากบัคเก็ต AWS S3 จากนั้น เมื่อสร้างไฟล์ขึ้นใหม่ เซิร์ฟเวอร์จะอัปโหลดไฟล์ใหม่ไปยัง S3 ทันทีและให้บริการจากที่นั่น การใช้งานโซลูชันนี้มีอธิบายไว้ในบทความก่อนหน้าของฉัน การแบ่งปันข้อมูลระหว่างเซิร์ฟเวอร์หลายเครื่องผ่าน AWS S3

บทสรุป

ในบทความนี้ เราได้พิจารณาแล้วว่าโค้ด inlining JS และ CSS นั้นไม่เหมาะเสมอไป เนื่องจากต้องส่งโค้ดซ้ำๆ ไปยังไคลเอนต์ ซึ่งอาจส่งผลต่อประสิทธิภาพได้หากจำนวนโค้ดมีนัยสำคัญ ตัวอย่างเช่น เราเห็นวิธีที่ WordPress โหลดสคริปต์ 43kb เพื่อพิมพ์ Media Manager ซึ่งเป็นเทมเพลต JavaScript แท้ ๆ และสามารถโหลดเป็นทรัพยากรแบบคงที่ได้อย่างสมบูรณ์แบบ

ดังนั้นเราจึงได้คิดค้นวิธีทำให้เว็บไซต์เร็วขึ้นโดยการแปลงโค้ดอินไลน์ JS และ CSS แบบไดนามิกให้เป็นทรัพยากรแบบสแตติก ซึ่งสามารถปรับปรุงการแคชได้หลายระดับ (ในไคลเอนต์ Service Workers, CDN) ช่วยให้รวมไฟล์ทั้งหมดเข้าด้วยกันเพิ่มเติม ลงในทรัพยากร JS/CSS เพียงแห่งเดียวเพื่อปรับปรุงอัตราส่วนเมื่อบีบอัดเอาต์พุต (เช่นผ่าน GZip) และเพื่อหลีกเลี่ยงโอเวอร์เฮดในเบราว์เซอร์จากการประมวลผลทรัพยากรหลายอย่างพร้อมกัน (เช่นใน Chrome) และยังอนุญาตให้เพิ่มแอตทริบิวต์ async หรือ defer ไปที่แท็ก <script> เพื่อเพิ่มความเร็วในการโต้ตอบของผู้ใช้ ซึ่งจะช่วยปรับปรุงเวลาในการโหลดของไซต์อย่างเห็นได้ชัด

ในฐานะที่เป็นผลข้างเคียงที่เป็นประโยชน์ การแบ่งโค้ดออกเป็นทรัพยากรแบบคงที่ยังช่วยให้โค้ดอ่านง่ายขึ้น โดยจัดการกับหน่วยของโค้ดแทนที่จะเป็น HTML ขนาดใหญ่ ซึ่งสามารถนำไปสู่การบำรุงรักษาโปรเจ็กต์ได้ดีขึ้น

โซลูชันที่เราพัฒนาขึ้นนั้นเสร็จสิ้นใน PHP และรวมถึงโค้ดบางบิตสำหรับ WordPress อย่างไรก็ตาม โค้ดนั้นง่ายมาก อินเทอร์เฟซไม่กี่ตัวที่กำหนดคุณสมบัติและอ็อบเจ็กต์ที่ใช้คุณสมบัติเหล่านั้นตามหลักการ SOLID และฟังก์ชันสำหรับบันทึก ไฟล์ลงดิสก์ ประมาณนั้นครับ ผลลัพธ์ที่ได้คือสะอาดและกะทัดรัด ตรงไปตรงมาเพื่อสร้างใหม่สำหรับภาษาและแพลตฟอร์มอื่น ๆ และไม่ยากที่จะนำไปใช้กับโครงการที่มีอยู่ — ให้ประสิทธิภาพที่เพิ่มขึ้นง่าย