หลีกเลี่ยงหลุมพรางของโค้ดแบบอินไลน์อัตโนมัติ
เผยแพร่แล้ว: 2022-03-10 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
) การพิมพ์โค้ดจำนวนมาก:
ด้วยขนาดเต็ม 43kb ขนาดของโค้ดนี้จึงไม่สำคัญ และเนื่องจากโค้ดนี้อยู่ที่ด้านล่างของหน้าจึงไม่จำเป็นในทันที ดังนั้นจึงควรแสดงโค้ดนี้ผ่านเนื้อหาสแตติกแทนหรือพิมพ์โค้ดนี้ในเอาต์พุต HTML
มาดูวิธีการแปลงโค้ดอินไลน์เป็นสแตติกแอสเซ็ทกันต่อไป
ทริกเกอร์การสร้างไฟล์คงที่
หากเนื้อหา (ที่จะถูกแทรกในบรรทัด) มาจากไฟล์สแตติก ก็ไม่ต้องทำอะไรมากนอกจากขอไฟล์สแตติกนั้นแทนการอินไลน์โค้ด
สำหรับโค้ดไดนามิก เราต้องวางแผนว่าจะสร้างไฟล์สแตติกพร้อมกับเนื้อหาอย่างไร/เมื่อใด ตัวอย่างเช่น หากไซต์มีตัวเลือกการกำหนดค่า (เช่น การเปลี่ยนรูปแบบสีหรือภาพพื้นหลัง) เมื่อใดควรสร้างไฟล์ที่มีค่าใหม่ เรามีโอกาสสร้างไฟล์สแตติกจากโค้ดไดนามิกดังต่อไปนี้:
- ตามคำขอร้อง
เมื่อผู้ใช้เข้าถึงเนื้อหาเป็นครั้งแรก - เมื่อมีการเปลี่ยนแปลง
เมื่อซอร์สโค้ดไดนามิก (เช่น ค่าคอนฟิกูเรชัน) มีการเปลี่ยนแปลง
พิจารณาตามคำขอก่อน ครั้งแรกที่ผู้ใช้เข้าถึงไซต์ สมมติว่าผ่าน /index.html
ไฟล์สแตติก (เช่น header-colors.css
) ยังไม่มีอยู่ ดังนั้นจึงต้องสร้างขึ้น ลำดับเหตุการณ์มีดังต่อไปนี้:
- ผู้ใช้ร้องขอ
/index.html
; - เมื่อประมวลผลคำขอ เซิร์ฟเวอร์จะตรวจสอบว่าไฟล์
header-colors.css
มีอยู่หรือไม่ เนื่องจากไม่ได้รับซอร์สโค้ดและสร้างไฟล์บนดิสก์ - ส่งคืนการตอบกลับไปยังไคลเอนต์ รวมถึงแท็ก
<link rel="stylesheet" type="text/css" href="/staticfiles/header-colors.css">
- เบราว์เซอร์ดึงทรัพยากรทั้งหมดที่รวมอยู่ในหน้า รวมถึง
header-colors.css
; - เมื่อถึงตอนนั้น ไฟล์นี้จึงมีอยู่ ดังนั้นจึงให้บริการ
อย่างไรก็ตาม ลำดับของเหตุการณ์ก็อาจแตกต่างกัน นำไปสู่ผลลัพธ์ที่ไม่น่าพอใจ ตัวอย่างเช่น:
- ผู้ใช้ร้องขอ
/index.html
; - ไฟล์นี้ถูกแคชไว้โดยเบราว์เซอร์ (หรือพร็อกซีอื่นๆ หรือผ่าน Service Workers) ดังนั้นคำขอจะไม่ถูกส่งไปยังเซิร์ฟเวอร์
- เบราว์เซอร์ดึงทรัพยากรทั้งหมดที่รวมอยู่ในหน้า รวมถึง
header-colors.css
อย่างไรก็ตาม อิมเมจนี้ไม่ได้ถูกแคชในเบราว์เซอร์ ดังนั้นคำขอจะถูกส่งไปยังเซิร์ฟเวอร์ - เซิร์ฟเวอร์ยังไม่ได้สร้าง
header-colors.css
(เช่น เพิ่งรีสตาร์ท) - มันจะส่งคืน 404
หรืออีกทางหนึ่ง เราสามารถสร้าง header-colors.css
ไม่ใช่เมื่อร้องขอ /index.html
แต่เมื่อร้องขอ /header-colors.css
เอง อย่างไรก็ตาม เนื่องจากในตอนแรกไฟล์นี้ไม่มีอยู่ คำขอจึงได้รับการปฏิบัติเป็น 404 แม้ว่าเราจะสามารถแฮ็กข้อมูลได้ เปลี่ยนส่วนหัวเพื่อเปลี่ยนรหัสสถานะเป็น 200 และส่งคืนเนื้อหาของรูปภาพ นี่เป็นวิธีการทำสิ่งต่าง ๆ ที่แย่มาก ดังนั้นเราจะไม่สนุกกับความเป็นไปได้นี้ (เราดีกว่านี้มาก!)
เหลือตัวเลือกเดียวเท่านั้น: การสร้างไฟล์สแตติกหลังจากเปลี่ยนแหล่งที่มา
การสร้างไฟล์คงที่เมื่อแหล่งที่มาเปลี่ยนแปลง
โปรดสังเกตว่า เราสามารถสร้างโค้ดไดนามิกจากแหล่งที่มาทั้งที่ขึ้นกับผู้ใช้และที่ขึ้นกับไซต์ ตัวอย่างเช่น หากธีมอนุญาตให้เปลี่ยนรูปภาพพื้นหลังของไซต์และตัวเลือกนั้นได้รับการกำหนดค่าโดยผู้ดูแลระบบของไซต์ ไฟล์สแตติกก็สามารถสร้างเป็นส่วนหนึ่งของกระบวนการปรับใช้ได้ ในทางกลับกัน หากไซต์อนุญาตให้ผู้ใช้เปลี่ยนภาพพื้นหลังสำหรับโปรไฟล์ของพวกเขา ไฟล์สแตติกจะต้องถูกสร้างขึ้นบนรันไทม์
สรุปเรามีสองกรณีนี้:
- การกำหนดค่าผู้ใช้
กระบวนการต้องถูกทริกเกอร์เมื่อผู้ใช้อัปเดตการกำหนดค่า - การกำหนดค่าไซต์
กระบวนการต้องถูกทริกเกอร์เมื่อผู้ดูแลระบบอัปเดตการกำหนดค่าสำหรับไซต์ หรือก่อนที่จะปรับใช้ไซต์
หากเราพิจารณาทั้งสองกรณีแยกกัน สำหรับ #2 เราสามารถออกแบบกระบวนการบนสแต็กเทคโนโลยีใดก็ได้ที่เราต้องการ อย่างไรก็ตาม เราไม่ต้องการใช้โซลูชันที่แตกต่างกันสองวิธี แต่เป็นโซลูชันที่ไม่เหมือนใครซึ่งสามารถจัดการกับทั้งสองกรณีได้ และเนื่องจากตั้งแต่ #1 กระบวนการในการสร้างไฟล์สแตติกจะต้องถูกทริกเกอร์บนไซต์ที่ทำงานอยู่ ดังนั้นจึงเป็นเรื่องที่น่าสนใจที่จะออกแบบกระบวนการนี้โดยใช้เทคโนโลยีเดียวกันกับสแต็คที่ไซต์ทำงานอยู่
เมื่อออกแบบกระบวนการ โค้ดของเราจะต้องจัดการกับสถานการณ์เฉพาะของทั้ง #1 และ #2:
- การกำหนดเวอร์ชัน
ไฟล์สแตติกต้องเข้าถึงได้ด้วยพารามิเตอร์ "เวอร์ชัน" เพื่อทำให้ไฟล์ก่อนหน้าเป็นโมฆะเมื่อมีการสร้างไฟล์สแตติกใหม่ แม้ว่า #2 อาจมีการกำหนดเวอร์ชันเดียวกันกับไซต์ แต่ #1 ต้องใช้เวอร์ชันไดนามิกสำหรับผู้ใช้แต่ละราย ซึ่งอาจบันทึกไว้ในฐานข้อมูล - ตำแหน่งของไฟล์ที่สร้างขึ้น
#2 สร้างไฟล์สแตติกที่ไม่ซ้ำกันสำหรับทั้งไซต์ (เช่น/staticfiles/header-colors.css
) ในขณะที่ #1 สร้างไฟล์สแตติกสำหรับผู้ใช้แต่ละคน (เช่น/staticfiles/users/leo/header-colors.css
) - เหตุการณ์ทริกเกอร์
ในขณะที่ #1 ไฟล์สแตติกต้องถูกเรียกใช้งานบนรันไทม์ สำหรับ #2 ไฟล์นั้นสามารถถูกเรียกใช้งานโดยเป็นส่วนหนึ่งของกระบวนการบิลด์ในสภาพแวดล้อมการจัดเตรียมของเรา - การปรับใช้และการจัดจำหน่าย
ไฟล์สแตติกใน #2 สามารถรวมเข้าด้วยกันอย่างราบรื่นภายในบันเดิลการปรับใช้ของไซต์ โดยไม่มีความท้าทาย อย่างไรก็ตาม ไฟล์สแตติกใน #1 ทำไม่ได้ ดังนั้นกระบวนการจึงต้องจัดการกับข้อกังวลเพิ่มเติม เช่น เซิร์ฟเวอร์หลายตัวที่อยู่เบื้องหลังโหลดบาลานเซอร์ (ไฟล์สแตติกจะถูกสร้างขึ้นใน 1 เซิร์ฟเวอร์เท่านั้นหรือทั้งหมด และอย่างไร)
มาออกแบบและดำเนินการตามกระบวนการต่อไป ในการสร้างไฟล์สแตติกแต่ละไฟล์ เราต้องสร้างอ็อบเจ็กต์ที่มีข้อมูลเมตาของไฟล์ คำนวณเนื้อหาจากแหล่งที่มาแบบไดนามิก และสุดท้ายบันทึกไฟล์สแตติกลงในดิสก์ เป็นกรณีการใช้งานเพื่อเป็นแนวทางในการอธิบายด้านล่าง เราจะสร้างไฟล์สแตติกต่อไปนี้:
-
header-colors.css
พร้อมสไตล์บางส่วนจากค่าที่บันทึกไว้ในฐานข้อมูล -
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 และฟังก์ชันสำหรับบันทึก ไฟล์ลงดิสก์ ประมาณนั้นครับ ผลลัพธ์ที่ได้คือสะอาดและกะทัดรัด ตรงไปตรงมาเพื่อสร้างใหม่สำหรับภาษาและแพลตฟอร์มอื่น ๆ และไม่ยากที่จะนำไปใช้กับโครงการที่มีอยู่ — ให้ประสิทธิภาพที่เพิ่มขึ้นง่าย