Codelab นี้จะสำรวจว่าทั้งการลดขนาดและการบีบอัดแพ็กเกจ JavaScript สำหรับแอปพลิเคชันต่อไปนี้ช่วยปรับปรุงประสิทธิภาพของหน้าด้วยการลดขนาดคำขอของแอปอย่างไร
วัดระยะทาง
ก่อนจะเจาะลึกเพื่อเพิ่มการเพิ่มประสิทธิภาพ คุณควรวิเคราะห์สถานะปัจจุบันของแอปพลิเคชันก่อน
- หากต้องการดูตัวอย่างเว็บไซต์ ให้กดดูแอป แล้วกดเต็มหน้าจอ
แอปนี้ซึ่งอยู่ใน Codelab "นำโค้ดที่ไม่ได้ใช้ออก" ให้คุณโหวตให้กับลูกแมวตัวโปรดได้ 🐈
มาดูขนาดของแอปพลิเคชันนี้กัน
- กดแป้น Control+Shift+J (หรือ Command+Option+J ใน Mac) เพื่อเปิดเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์
- คลิกแท็บเครือข่าย
- เลือกช่องทำเครื่องหมายปิดใช้แคช
- โหลดแอปซ้ำ
แม้ว่าจะมีความคืบหน้าอย่างมากใน"นำโค้ดที่ไม่ได้ใช้ออก" ทาง Codelab เพื่อลดขนาดของ Bundle นี้ แต่ 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 ของเบราว์เซอร์จะตีความข้อมูลเหล่านี้ในลักษณะเดียวกัน ข้อดีของการสร้างความสับสนให้กับโค้ดในลักษณะนี้ช่วยให้ไฟล์มีขนาดเล็กลงได้ 112 B นั้นถือว่าไม่มากนักตั้งแต่แรก แต่ก็ยังลดขนาดได้ 50%
ในแอปพลิเคชันนี้ ระบบจะใช้ webpack เวอร์ชัน 4 เป็นโปรแกรมรวมโมดูล คุณดูเวอร์ชันที่เจาะจงได้ใน package.json
"devDependencies": {
//...
"webpack": "^4.16.4",
//...
}
เวอร์ชัน 4 จะลดขนาดแพ็กเกจโดยค่าเริ่มต้นอยู่แล้วในโหมดการใช้งานจริง โดยใช้
TerserWebpackPlugin
ปลั๊กอินสำหรับ Terser
Terser เป็นเครื่องมือที่ได้รับความนิยมในการบีบอัดโค้ด JavaScript
หากต้องการดูว่าโค้ดที่บีบอัดแล้วมีลักษณะเป็นอย่างไร ให้คลิก main.bundle.js
ขณะยังอยู่ในแผงเครือข่ายของ DevTools จากนั้นคลิกแท็บการตอบกลับ
โค้ดในรูปแบบสุดท้ายซึ่งได้รับการย่อขนาดและทำให้อ่านไม่ออกจะแสดงอยู่ในเนื้อหาของคำตอบ
หากต้องการดูขนาดที่เป็นไปได้ของ Bundle หากไม่ได้มีการย่อขนาด ให้เปิด
webpack.config.js
แล้วอัปเดตการกำหนดค่า mode
module.exports = {
mode: 'production',
mode: 'none',
//...
โหลดแอปพลิเคชันอีกครั้งและดูขนาด Bundle อีกครั้งผ่านแผงเครือข่ายของเครื่องมือสำหรับนักพัฒนาเว็บ
ความแตกต่างนี้ค่อนข้างมาก 😅
โปรดเปลี่ยนการเปลี่ยนแปลงกลับที่นี่ก่อนดำเนินการต่อ
module.exports = {
mode: 'production',
mode: 'none',
//...
การรวมกระบวนการลดขนาดโค้ดในแอปพลิเคชันจะขึ้นอยู่กับเครื่องมือที่คุณใช้ ดังนี้
- หากใช้ webpack v4 ขึ้นไป ก็ไม่ต้องดําเนินการใดๆ เพิ่มเติมเนื่องจากระบบจะย่อโค้ดโดยค่าเริ่มต้นในโหมดเวอร์ชันที่ใช้งานจริง 👍
- หากใช้ webpack เวอร์ชันเก่า ให้ติดตั้งและรวม
TerserWebpackPlugin
ไว้ในกระบวนการบิลด์ webpack เอกสารประกอบอธิบายเรื่องนี้อย่างละเอียด - นอกจากนี้ ยังมีปลั๊กอินการลดขนาดอื่นๆ และใช้แทนได้ด้วย เช่น BabelMinifyWebpackPlugin และ ClosureCompilerPlugin
- หากไม่ได้ใช้ Bundler โมดูลเลย ให้ใช้ Terser เป็นเครื่องมือ CLI หรือรวมเป็นทรัพยากร Dependency โดยตรง
การบีบอัด
แม้ว่าบางครั้งเราจะใช้คำว่า "การบีบอัด" อย่างหลวมๆ เพื่ออธิบายวิธีลดโค้ดในระหว่างกระบวนการ Minify แต่จริงๆ แล้วไม่ได้มีการบีบอัดในความหมายที่แท้จริง
การบีบอัดมักจะหมายถึงโค้ดที่มีการแก้ไขโดยใช้อัลกอริทึมการบีบอัดข้อมูล คุณจะต้องคลายการบีบอัดโค้ดที่บีบอัดก่อนที่จะใช้งาน ซึ่งต่างจากการลดขนาดลงซึ่งสุดท้ายแล้วจะมอบโค้ดที่ถูกต้องสมบูรณ์
เมื่อมีการขอและตอบกลับ HTTP ทุกครั้ง เบราว์เซอร์และเว็บเซิร์ฟเวอร์จะเพิ่มส่วนหัวเพื่อรวมข้อมูลเพิ่มเติมเกี่ยวกับชิ้นงานที่ดึงข้อมูลหรือได้รับ ข้อมูลนี้อยู่ในแท็บ Headers
ในแผงเครือข่ายเครื่องมือสำหรับนักพัฒนาเว็บ ซึ่งแสดง 3 ประเภท ดังนี้
- ทั่วไปแสดงส่วนหัวทั่วไปที่เกี่ยวข้องกับการโต้ตอบคำขอและการตอบกลับทั้งหมด
- ส่วนหัวการตอบกลับแสดงรายการส่วนหัวเฉพาะสำหรับการตอบกลับจริงจากเซิร์ฟเวอร์
- ส่วนหัวของคำขอแสดงรายการส่วนหัวที่ไคลเอ็นต์แนบมากับคำขอ
ดูส่วนหัว accept-encoding
ใน Request Headers
accept-encoding
ใช้โดยเบราว์เซอร์เพื่อระบุรูปแบบการเข้ารหัสเนื้อหาหรืออัลกอริทึมการบีบอัดที่รองรับ อัลกอริทึมการบีบอัดข้อความมีอยู่หลายรายการ แต่มีเพียง 3 รายการที่รองรับการบีบอัด (และการขยายข้อมูลที่บีบอัด) ของคําขอเครือข่าย HTTP ดังนี้
- Gzip (
gzip
): รูปแบบการบีบอัดที่ใช้กันอย่างแพร่หลายที่สุดสำหรับการโต้ตอบระหว่างเซิร์ฟเวอร์กับไคลเอ็นต์ ซึ่งพัฒนาต่อยอดมาจากอัลกอริทึม Deflate และเบราว์เซอร์ทุกรุ่นในปัจจุบันรองรับ - Deflate (
deflate
): ไม่ค่อยได้ใช้ - Brotli (
br
): อัลกอริทึมการบีบอัดที่ใหม่กว่าซึ่งมุ่งปรับปรุงอัตราส่วนการบีบอัดให้ดียิ่งขึ้น ซึ่งอาจส่งผลให้หน้าเว็บโหลดได้เร็วขึ้น ฟีเจอร์นี้ใช้ได้ในเบราว์เซอร์ส่วนใหญ่เวอร์ชันล่าสุด
แอปพลิเคชันตัวอย่างในบทแนะนำนี้เหมือนกับแอปที่เสร็จสมบูรณ์ใน Codelab "นำโค้ดที่ไม่ได้ใช้ออก" ยกเว้นตอนนี้ใช้ Express เป็นเฟรมเวิร์กเซิร์ฟเวอร์ ส่วนถัดไปจะกล่าวถึงทั้งการบีบอัดแบบคงที่และแบบไดนามิก
การบีบอัดแบบไดนามิก
การบีบอัดแบบไดนามิกเกี่ยวข้องกับการบีบอัดชิ้นงานขณะที่เบราว์เซอร์ขอ
ข้อดี
- คุณไม่จำเป็นต้องสร้างและอัปเดตเนื้อหาเวอร์ชันบีบอัดที่บันทึกไว้
- การบีบอัดขณะใช้งานเหมาะอย่างยิ่งสําหรับหน้าเว็บที่สร้างแบบไดนามิก
ข้อเสีย
- การบีบอัดไฟล์ในระดับที่สูงขึ้นเพื่อให้ได้อัตราส่วนการบีบอัดที่ดีขึ้นจะใช้เวลานานขึ้น ซึ่งอาจทำให้เกิดการเกิด Performance Hit ขึ้น เนื่องจากผู้ใช้ต้องรอให้เนื้อหาบีบอัดก่อนที่เซิร์ฟเวอร์จะส่งเนื้อหาเหล่านั้น
การบีบอัดแบบไดนามิกด้วย Node/Express
ไฟล์ server.js
มีหน้าที่รับผิดชอบในการตั้งค่าเซิร์ฟเวอร์ Node ที่โฮสต์แอปพลิเคชัน
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'));
//...
จากนั้นโหลดแอปอีกครั้งและดูขนาด Bundle ในแผงเครือข่าย
จาก 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
ดูเอกสารประกอบเพื่อดูวิธีเพิ่มตัวเลือกเพื่อใช้อัลกอริทึมอื่น หรือรวม/ยกเว้นบางไฟล์
เมื่อแอปโหลดซ้ำและสร้างใหม่ ระบบจะสร้างแพ็กเกจหลักเวอร์ชันที่บีบอัดแล้ว เปิด Glitch Console เพื่อดูสิ่งที่อยู่ในไดเรกทอรี public/
สุดท้ายที่เซิร์ฟเวอร์ Node แสดง
- คลิกปุ่มเครื่องมือ
- คลิกปุ่มคอนโซล
- ในคอนโซล ให้เรียกใช้คําสั่งต่อไปนี้เพื่อเปลี่ยนเป็นไดเรกทอรี
public
และดูไฟล์ทั้งหมดในไดเรกทอรี
cd public
ls
ระบบจะบันทึก Bundle main.bundle.js.gz
เวอร์ชันที่บีบอัดข้อมูลแล้วไว้ที่นี่ด้วย CompressionPlugin
จะบีบอัด index.html
โดยค่าเริ่มต้นด้วย
ขั้นตอนถัดไปคือบอกเซิร์ฟเวอร์ให้ส่งไฟล์ที่บีบอัดเหล่านี้ทุกครั้งที่มีการขอ 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 - ภายในการเรียกกลับ ระบบจะแนบ
.gz
กับ URL ของคำขอและตั้งค่าส่วนหัวของคำตอบContent-Encoding
เป็นgzip
- สุดท้าย
next()
จะตรวจสอบว่าลําดับจะดําเนินการต่อไปยังการเรียกกลับที่อาจเกิดขึ้นถัดไป
เมื่อโหลดแอปซ้ำแล้ว ให้ดูที่แผง Network
อีกครั้ง
เช่นเดียวกับก่อนหน้านี้ ขนาดของ Bundle ลดลงอย่างมาก
บทสรุป
Codelab นี้ครอบคลุมกระบวนการลดขนาดและบีบอัดซอร์สโค้ด เทคนิคทั้ง 2 แบบนี้กำลังกลายเป็นค่าเริ่มต้นในเครื่องมือจํานวนมากที่มีให้บริการในปัจจุบัน คุณจึงควรตรวจสอบว่าเครื่องมือทางเทคนิคของคุณรองรับเทคนิคเหล่านี้อยู่แล้ว หรือคุณควรเริ่มใช้ทั้ง 2 กระบวนการด้วยตนเอง