Codelab นี้จะสำรวจว่าทั้งการลดขนาดและการบีบอัดแพ็กเกจ JavaScript สำหรับแอปพลิเคชันต่อไปนี้ช่วยปรับปรุงประสิทธิภาพของหน้าด้วยการลดขนาดคำขอของแอปอย่างไร
วัดระยะทาง
ก่อนจะเจาะลึกเพื่อเพิ่มการเพิ่มประสิทธิภาพ คุณควรวิเคราะห์สถานะปัจจุบันของแอปพลิเคชันก่อน
- หากต้องการดูตัวอย่างเว็บไซต์ ให้กดดูแอป แล้วกด เต็มหน้าจอ
แอปนี้ซึ่งอยู่ใน Codelab "นำโค้ดที่ไม่ได้ใช้ออก" ด้วย ซึ่งให้คุณโหวตให้ลูกแมวตัวโปรดของคุณ 🐈
ต่อไปมาดูกันว่าแอปพลิเคชันนี้มีขนาดใหญ่แค่ไหน
- กด "Control+Shift+J" (หรือ "Command+Option+J" ใน Mac) เพื่อเปิดเครื่องมือสำหรับนักพัฒนาเว็บ
- คลิกแท็บเครือข่าย
- เลือกช่องทำเครื่องหมายปิดใช้แคช
- โหลดแอปซ้ำ
แม้ว่าโค้ดแล็บ "นำโค้ดที่ไม่ได้ใช้งานออก" จะมีความคืบหน้าไปมากมายเพื่อลดขนาดแพ็กเกจนี้ลง แต่ 225 KB ก็ยังถือว่าใหญ่อยู่มาก
การทำให้มีขนาดเล็กลง
ลองพิจารณาบล็อกโค้ดต่อไปนี้
function soNice() {
let counter = 0;
while (counter < 100) {
console.log('nice');
counter++;
}
}
หากบันทึกฟังก์ชันนี้เป็นไฟล์ของตนเอง จะมีขนาดไฟล์ประมาณ 112 B (ไบต์)
หากนำช่องว่างออกหมดแล้ว โค้ดที่ได้จะมีลักษณะดังนี้
function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}
ขนาดไฟล์ตอนนี้จะอยู่ที่ประมาณ 83 B หากยังแก้ไขให้สับสนอีกโดยการลดความยาวของชื่อตัวแปรและแก้ไขนิพจน์บางรายการ โค้ดสุดท้ายอาจมีลักษณะดังนี้
function soNice(){for(let i=0;i<100;)console.log("nice"),i++}
ขณะนี้ไฟล์มีขนาดถึง 62 B
ในแต่ละขั้นตอน รหัสดังกล่าวจะอ่านยากขึ้น อย่างไรก็ตาม เครื่องมือ JavaScript ของเบราว์เซอร์จะตีความข้อมูลเหล่านี้ในลักษณะเดียวกัน ประโยชน์ของโค้ดที่ปรับให้ยากต่อการอ่าน (Obfuscate) ด้วยวิธีนี้จะช่วยให้ไฟล์มีขนาดเล็กลงได้ จริงๆ แล้ว 112 B ไม่ได้เริ่มต้นมากนัก แต่ยังคงสามารถลดขนาดลงได้ 50%!
ในแอปพลิเคชันนี้ ระบบจะใช้ webpack เวอร์ชัน 4 เป็น Bundler โมดูล ดูเวอร์ชันที่เฉพาะเจาะจงได้ใน package.json
"devDependencies": {
//...
"webpack": "^4.16.4",
//...
}
เวอร์ชัน 4 จะลดขนาดแพ็กเกจโดยค่าเริ่มต้นอยู่แล้วในโหมดการใช้งานจริง โดยใช้ปลั๊กอิน TerserWebpackPlugin
สำหรับ Terser
Terser เป็นเครื่องมือที่ได้รับความนิยมในการบีบอัดโค้ด JavaScript
หากต้องการดูว่าโค้ดที่ลดขนาดลงมีลักษณะอย่างไร ให้คลิก main.bundle.js
ขณะที่ยังอยู่ในแผงเครือข่ายของเครื่องมือสำหรับนักพัฒนาเว็บ จากนั้นคลิกแท็บการตอบกลับ
โค้ดในรูปแบบสุดท้ายซึ่งถูกลดขนาดลงและเสียหาย จะแสดงในเนื้อหาการตอบสนอง
หากต้องการดูว่าแพ็กเกจอาจมีขนาดใหญ่แค่ไหนหากไม่ได้ลดขนาด ให้เปิด webpack.config.js
แล้วอัปเดตการกำหนดค่า mode
module.exports = {
mode: 'production',
mode: 'none',
//...
โหลดแอปพลิเคชันซ้ำและดูขนาดแพ็กเกจอีกครั้งผ่านแผงเครือข่ายเครื่องมือสำหรับนักพัฒนาเว็บ
นับว่าแตกต่างไปค่อนข้างมาก 😅
โปรดตรวจสอบว่าได้เปลี่ยนกลับการเปลี่ยนแปลงที่นี่ก่อนดำเนินการต่อ
module.exports = {
mode: 'production',
mode: 'none',
//...
การรวมกระบวนการลดขนาดโค้ดในแอปพลิเคชันจะขึ้นอยู่กับเครื่องมือที่คุณใช้ ดังนี้
- หากใช้ Webpack v4 ขึ้นไป คุณไม่จําเป็นต้องดําเนินการใดๆ เพิ่มเติมเนื่องจากระบบจะลดขนาดโค้ดโดยค่าเริ่มต้นในโหมดการใช้งานจริง 👍
- หากใช้ Webpack เวอร์ชันเก่า ให้ติดตั้งและเพิ่ม
TerserWebpackPlugin
ลงในกระบวนการสร้าง Webpack เอกสารประกอบจะอธิบายเรื่องนี้โดยละเอียด - นอกจากนี้ ยังมีปลั๊กอินการลดขนาดอื่นๆ และใช้แทนได้ด้วย เช่น BabelMinifyWebpackPlugin และ ClosureCompilerPlugin
- หากไม่ได้ใช้ Bundler โมดูลเลย ให้ใช้ Terser เป็นเครื่องมือ CLI หรือรวมเป็นทรัพยากร Dependency โดยตรง
การบีบอัด
แม้ว่าบางครั้งจะใช้คำว่า "การบีบอัด" เพื่ออธิบายว่ามีการลดโค้ดในระหว่างขั้นตอนการลดขนาดอย่างไร แต่จริงๆ แล้วไม่ได้บีบอัดตามตัวอักษร
การบีบอัดมักจะหมายถึงโค้ดที่มีการแก้ไขโดยใช้อัลกอริทึมการบีบอัดข้อมูล โค้ดที่บีบอัดจะต้องคลายการบีบอัดก่อนใช้งาน ซึ่งต่างจากการลดขนาดลงซึ่งสุดท้ายแล้วจะมอบโค้ดที่ถูกต้องสมบูรณ์
ในคำขอ HTTP และการตอบกลับทุกรายการ เบราว์เซอร์และเว็บเซิร์ฟเวอร์จะเพิ่มheadersเพื่อใส่ข้อมูลเพิ่มเติมเกี่ยวกับเนื้อหาที่กำลังดึงหรือได้รับได้ ข้อมูลนี้อยู่ในแท็บ Headers
ในแผงเครือข่ายเครื่องมือสำหรับนักพัฒนาเว็บ ซึ่งแสดง 3 ประเภท ดังนี้
- General แสดงส่วนหัวทั่วไปที่เกี่ยวข้องกับการโต้ตอบคำขอตอบกลับทั้งหมด
- ส่วนหัวการตอบกลับจะแสดงรายการส่วนหัวเฉพาะสำหรับการตอบกลับจริงจากเซิร์ฟเวอร์
- ส่วนหัวของคำขอแสดงรายการส่วนหัวที่แนบมากับคำขอโดยไคลเอ็นต์
ดูส่วนหัว accept-encoding
ใน Request Headers
เบราว์เซอร์ใช้ accept-encoding
เพื่อระบุรูปแบบการเข้ารหัสเนื้อหาหรืออัลกอริทึมการบีบอัดที่เบราว์เซอร์รองรับ อัลกอริทึมการบีบอัดข้อความมีอยู่มากมาย แต่มีอัลกอริทึมเพียง 3 รายการเท่านั้นที่รองรับในการบีบอัด (และการขยายการบีบอัด) ของคำขอเครือข่าย HTTP ดังนี้
- Gzip (
gzip
): รูปแบบการบีบอัดที่ใช้กันอย่างแพร่หลายสำหรับการโต้ตอบระหว่างเซิร์ฟเวอร์และไคลเอ็นต์ เวอร์ชันดังกล่าวสร้างขึ้นจากอัลกอริทึม Deflate และรองรับในเบราว์เซอร์ปัจจุบันทั้งหมด - Deflate (
deflate
): ไม่นิยมใช้ - Brotli (
br
): อัลกอริทึมการบีบอัดแบบใหม่ที่มีเป้าหมายเพื่อปรับปรุงอัตราส่วนการบีบอัดข้อมูลให้ดียิ่งขึ้นซึ่งอาจส่งผลให้หน้าเว็บโหลดได้เร็วขึ้น ซึ่งใช้ได้ในเบราว์เซอร์ส่วนใหญ่ในเวอร์ชันล่าสุด
แอปพลิเคชันตัวอย่างในบทแนะนำนี้เหมือนกับแอปที่เสร็จสมบูรณ์ใน Codelab "นำโค้ดที่ไม่ได้ใช้ออก" ยกเว้นว่าในขณะนี้มีการใช้ Express เป็นเฟรมเวิร์กของเซิร์ฟเวอร์ ในส่วนต่อๆ ไป จะมีการสำรวจการบีบอัดทั้งแบบคงที่และแบบไดนามิก
การบีบอัดแบบไดนามิก
การบีบอัดแบบไดนามิกเกี่ยวข้องกับการบีบอัดเนื้อหาอย่างต่อเนื่องตามที่เบราว์เซอร์ขอ
ข้อดี
- คุณไม่จำเป็นต้องสร้างและอัปเดตเนื้อหาเวอร์ชันบีบอัดที่บันทึกไว้
- การบีบอัดทันทีทำงานได้ดีเป็นพิเศษสำหรับหน้าเว็บที่สร้างขึ้นแบบไดนามิก
ข้อเสีย
- การบีบอัดไฟล์ในระดับที่สูงขึ้นเพื่อให้ได้อัตราส่วนการบีบอัดที่ดีขึ้นจะใช้เวลานานขึ้น ซึ่งอาจทำให้เกิด Conversion ด้านประสิทธิภาพเนื่องจากผู้ใช้รอให้เนื้อหาบีบอัดก่อนที่จะส่งโดยเซิร์ฟเวอร์
การบีบอัดแบบไดนามิกด้วยโหนด/ด่วน
ไฟล์ server.js
มีหน้าที่ในการตั้งค่าเซิร์ฟเวอร์โหนดที่โฮสต์แอปพลิเคชัน
const express = require('express');
const app = express();
app.use(express.static('public'));
const listener = app.listen(process.env.PORT, function() {
console.log('Your app is listening on port ' + listener.address().port);
});
ปัจจุบันเหลือการนำเข้า express
และใช้มิดเดิลแวร์ express.static
เพื่อโหลดไฟล์ HTML, JS และ CSS แบบคงที่ทั้งหมดในไดเรกทอรี public/
(และไฟล์เหล่านั้นสร้างโดย Webpack ในทุกบิลด์)
เพื่อให้มั่นใจว่าระบบจะบีบอัดเนื้อหาทั้งหมดทุกครั้งที่มีการขอ คุณสามารถใช้ไลบรารีมิดเดิลแวร์การบีบอัดได้ เริ่มต้นด้วยการเพิ่มเป็น devDependency
ใน package.json
:
"devDependencies": {
//...
"compression": "^1.7.3"
},
และนำเข้าข้อมูลลงในไฟล์เซิร์ฟเวอร์ server.js
:
const express = require('express');
const compression = require('compression');
และเพิ่มเป็นมิดเดิลแวร์ก่อนจะต่อเชื่อม express.static
ดังนี้
//...
const app = express();
app.use(compression());
app.use(express.static('public'));
//...
ตอนนี้ให้โหลดแอปซ้ำแล้วดูขนาดแพ็กเกจในแผงเครือข่าย
จาก 225 KB เป็น 61.6 KB ตอนนี้ใน Response Headers
ส่วนหัว content-encoding
แสดงว่าเซิร์ฟเวอร์ส่งไฟล์ที่เข้ารหัสด้วย gzip
ลงมา
การบีบอัดแบบคงที่
แนวคิดที่อยู่เบื้องหลังการบีบอัดแบบคงที่คือให้บีบอัดเนื้อหาไว้และประหยัดเวลาก่อน
ข้อดี
- ไม่ต้องกังวลเรื่องเวลาในการตอบสนองเนื่องจากระดับการบีบอัดที่สูงอีกต่อไป ไม่จำเป็นต้องบีบอัดไฟล์แบบเรียลไทม์ เพราะดึงข้อมูลโดยตรงได้แล้ว
ข้อเสีย
- ต้องบีบอัดเนื้อหาในทุกบิลด์ เวลาในการสร้างอาจเพิ่มขึ้นอย่างมากหากใช้ระดับการบีบอัดที่สูง
การบีบอัดแบบคงที่ด้วย Node/Express และ Webpack
เนื่องจากการบีบอัดแบบคงที่มีการบีบอัดไฟล์ล่วงหน้า จึงสามารถแก้ไขการตั้งค่า Webpack เพื่อบีบอัดเนื้อหาเป็นส่วนหนึ่งของขั้นตอนการสร้าง
CompressionPlugin
สำหรับกรณีนี้
เริ่มต้นด้วยการเพิ่มเป็น devDependency
ใน package.json
:
"devDependencies": {
//...
"compression-webpack-plugin": "^1.1.11"
},
เช่นเดียวกับปลั๊กอิน Webpack อื่นๆ ให้นำเข้าในไฟล์การกำหนดค่า
webpack.config.js:
const path = require("path");
//...
const CompressionPlugin = require("compression-webpack-plugin");
และรวมไว้ในอาร์เรย์ plugins
module.exports = {
//...
plugins: [
//...
new CompressionPlugin()
]
}
โดยค่าเริ่มต้น ปลั๊กอินจะบีบอัดไฟล์บิลด์โดยใช้ gzip
ดูเอกสารประกอบเพื่อดูวิธีเพิ่มตัวเลือกเพื่อใช้อัลกอริทึมอื่นหรือรวม/ยกเว้นบางไฟล์
เมื่อแอปโหลดซ้ำและสร้างใหม่ ระบบจะสร้างเวอร์ชันบีบอัดของ Bundle หลัก เปิดคอนโซล Glitch เพื่อดูสิ่งที่อยู่ภายในไดเรกทอรี public/
สุดท้ายที่เซิร์ฟเวอร์โหนดแสดง
- คลิกปุ่มเครื่องมือ
- คลิกปุ่มคอนโซล
- ในคอนโซล ให้เรียกใช้คำสั่งต่อไปนี้เพื่อเปลี่ยนเป็นไดเรกทอรี
public
และดูไฟล์ทั้งหมด
cd public
ls
ตอนนี้ระบบจะบันทึกแพ็กเกจ main.bundle.js.gz
เวอร์ชันที่ gzip ไว้ที่นี่เช่นกัน CompressionPlugin
จะบีบอัด index.html
โดยค่าเริ่มต้นด้วย
ขั้นตอนต่อไปที่ต้องทำคือ บอกให้เซิร์ฟเวอร์ส่งไฟล์ gzip เหล่านี้เมื่อใดก็ตามที่มีการขอ JS เวอร์ชันต้นฉบับ ซึ่งทำได้โดยกำหนดเส้นทางใหม่ใน server.js
ก่อนที่ไฟล์จะแสดงด้วย express.static
const express = require('express'); const app = express(); app.get('*.js', (req, res, next) => { req.url = req.url + '.gz'; res.set('Content-Encoding', 'gzip'); next(); }); app.use(express.static('public')); //...
app.get
ใช้เพื่อบอกเซิร์ฟเวอร์ถึงวิธีตอบสนองต่อคำขอ GET สำหรับปลายทางที่เฉพาะเจาะจง จากนั้นจะมีการใช้ฟังก์ชัน Callback เพื่อกำหนดวิธีจัดการคำขอนี้ โดยเส้นทางจะทำงานดังนี้
- การระบุ
'*.js'
เป็นอาร์กิวเมนต์แรกหมายความว่าระบบได้ผลกับปลายทางทุกปลายทางที่เริ่มทำงานเพื่อดึงไฟล์ JS - ภายใน Callback จะมีการแนบ
.gz
ไว้กับ URL ของคำขอและตั้งค่าส่วนหัวการตอบกลับContent-Encoding
เป็นgzip
- สุดท้าย
next()
จะดูแลให้ลำดับมีการเรียกกลับซึ่งอาจเป็นลำดับถัดไป
เมื่อโหลดแอปซ้ำแล้ว ให้ดูที่แผง Network
อีกครั้ง
เช่นเดียวกับก่อนหน้านี้ ขนาดแพ็กเกจจะลดลงอย่างมาก
บทสรุป
Codelab นี้ครอบคลุมกระบวนการลดขนาดและบีบอัดซอร์สโค้ด เทคนิคทั้ง 2 อย่างนี้กลายเป็นค่าเริ่มต้นในเครื่องมือจำนวนมากที่มีให้บริการในปัจจุบัน คุณจึงควรทราบว่าเครื่องมือเชนของคุณรองรับเครื่องมือดังกล่าวอยู่แล้วหรือไม่ หรือคุณควรเริ่มใช้ทั้ง 2 กระบวนการด้วยตัวเอง