โค้ดแล็บนี้จะสำรวจวิธีที่ทั้งการลดขนาดและการบีบอัด JavaScript Bundle สำหรับแอปพลิเคชันต่อไปนี้ช่วยปรับปรุงประสิทธิภาพของหน้าเว็บโดยการลดขนาดคำขอของแอป
วัดผล
ก่อนที่จะเจาะลึกเพื่อเพิ่มการเพิ่มประสิทธิภาพ คุณควรวิเคราะห์สถานะปัจจุบันของแอปพลิเคชันก่อนเสมอ
- หากต้องการดูตัวอย่างเว็บไซต์ ให้กดดูแอป แล้วกด
เต็มหน้าจอ
แอปนี้ซึ่งกล่าวถึงใน Codelab "นำโค้ดที่ไม่ได้ใช้ออก" ด้วย จะช่วยให้คุณโหวตลูกแมวตัวโปรดได้ 🐈
ตอนนี้มาดูขนาดของแอปพลิเคชันนี้กัน
- กด `Control+Shift+J` (หรือ `Command+Option+J` ใน Mac) เพื่อเปิด DevTools
- คลิกแท็บเครือข่าย
- เลือกช่องทำเครื่องหมายปิดใช้แคช
- โหลดแอปซ้ำ
แม้ว่าเราจะมีความคืบหน้าไปมากใน "นำโค้ดที่ไม่ได้ใช้ออก" โค้ดแล็บเพื่อลดขนาดแพ็กเกจนี้ แต่ 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 ไบต์ หากมีการดัดแปลงเพิ่มเติมโดยการลด ความยาวของชื่อตัวแปรและแก้ไขนิพจน์บางส่วน โค้ดสุดท้ายอาจ มีลักษณะดังนี้
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 จะย่อขนาด Bundle โดยค่าเริ่มต้นในโหมดที่ใช้งานจริงอยู่แล้ว ใช้
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
- หากไม่ได้ใช้เครื่องมือรวมโมดูลเลย ให้ใช้ Terser เป็นเครื่องมือ CLI หรือรวมเป็นทรัพยากร Dependency โดยตรง
การบีบอัด
แม้ว่าบางครั้งจะมีการใช้คำว่า "การบีบอัด" อย่างคร่าวๆ เพื่ออธิบายวิธีลดขนาดโค้ดในระหว่างกระบวนการลดขนาด แต่จริงๆ แล้วไม่ได้มีการบีบอัดในความหมายที่แท้จริง
การบีบอัดมักหมายถึงโค้ดที่ได้รับการแก้ไขโดยใช้อัลกอริทึมการบีบอัดข้อมูล โค้ดที่บีบอัดจะต้องคลายการบีบอัดก่อนจึงจะนำไปใช้ได้ ซึ่งแตกต่างจากการย่อขนาดที่ทำให้ได้โค้ดที่ถูกต้องอย่างสมบูรณ์
เมื่อมีคำขอและการตอบกลับ HTTP ทุกครั้ง เบราว์เซอร์และเว็บเซิร์ฟเวอร์จะเพิ่มส่วนหัวเพื่อรวมข้อมูลเพิ่มเติมเกี่ยวกับชิ้นงานที่ดึงข้อมูลหรือได้รับ ซึ่งดูได้ในแท็บ Headers
ภายในแผงเครือข่ายของเครื่องมือสำหรับนักพัฒนาเว็บ ซึ่งจะแสดง 3 ประเภทดังนี้
- ทั่วไปแสดงถึงส่วนหัวทั่วไปที่เกี่ยวข้องกับการโต้ตอบคำขอ-การตอบกลับทั้งหมด
- ส่วนหัวของการตอบกลับจะแสดงรายการส่วนหัวที่เฉพาะเจาะจงกับการตอบกลับจริง จากเซิร์ฟเวอร์
- ส่วนหัวของคำขอจะแสดงรายการส่วนหัวที่ไคลเอ็นต์แนบมากับคำขอ
ดูaccept-encoding
ส่วนหัวในRequest Headers
accept-encoding
ใช้โดยเบราว์เซอร์เพื่อระบุรูปแบบการเข้ารหัสเนื้อหาหรืออัลกอริทึมการบีบอัดที่รองรับ มีอัลกอริทึมการบีบอัดข้อความมากมาย
แต่มีเพียง 3 รายการที่รองรับที่นี่สำหรับการบีบอัด (และการขยายข้อมูลที่บีบอัด) คำขอเครือข่าย HTTP
- Gzip (
gzip
): รูปแบบการบีบอัดที่ใช้กันอย่างแพร่หลาย สำหรับการโต้ตอบระหว่างเซิร์ฟเวอร์กับไคลเอ็นต์ โดยพัฒนาต่อยอดมาจากอัลกอริทึม Deflate และรองรับในเบราว์เซอร์ปัจจุบันทั้งหมด - Deflate (
deflate
): ไม่นิยมใช้ - Brotli (
br
): อัลกอริทึมการบีบอัดที่ใหม่กว่า ซึ่งมีเป้าหมายเพื่อปรับปรุงอัตราส่วนการบีบอัดให้ดียิ่งขึ้น ซึ่งอาจส่งผลให้ หน้าเว็บโหลดได้เร็วยิ่งขึ้น โดยรองรับในเบราว์เซอร์ส่วนใหญ่เวอร์ชันล่าสุด
แอปพลิเคชันตัวอย่างในบทแนะนำนี้เหมือนกับแอปที่สร้างเสร็จใน Codelab "นำโค้ดที่ไม่ได้ใช้ออก" ทุกประการ ยกเว้นข้อเท็จจริงที่ว่าตอนนี้เราใช้ Express เป็นเฟรมเวิร์กเซิร์ฟเวอร์ ในส่วนถัดไป อีก 2-3 ส่วน เราจะมาดูการบีบอัดทั้งแบบคงที่และแบบไดนามิก
การบีบอัดแบบไดนามิก
การบีบอัดแบบไดนามิกเกี่ยวข้องกับการบีบอัดชิ้นงานทันทีเมื่อเบราว์เซอร์ร้องขอ
ข้อดี
- คุณไม่จำเป็นต้องสร้างและอัปเดตชิ้นงานเวอร์ชันที่บีบอัดที่บันทึกไว้
- การบีบอัดแบบเรียลไทม์เหมาะอย่างยิ่งสำหรับหน้าเว็บที่สร้างขึ้นแบบไดนามิก
ข้อเสีย
- การบีบอัดไฟล์ในระดับที่สูงขึ้นเพื่อให้ได้อัตราส่วนการบีบอัดที่ดีขึ้น จะใช้เวลานานกว่า ซึ่งอาจส่งผลต่อประสิทธิภาพเนื่องจากผู้ใช้ต้องรอให้ระบบบีบอัดชิ้นงานก่อนที่เซิร์ฟเวอร์จะส่งชิ้นงาน
การบีบอัดแบบไดนามิกด้วย 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 เวอร์ชันที่บีบอัดด้วย gzip, main.bundle.js.gz
ไว้ที่นี่ด้วยแล้ว
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 สำหรับปลายทางที่เฉพาะเจาะจงอย่างไร จากนั้นจะใช้ฟังก์ชันเรียกกลับเพื่อกำหนดวิธีจัดการคำขอนี้ เส้นทางจะทำงานดังนี้
- การระบุ
'*.js'
เป็นอาร์กิวเมนต์แรกหมายความว่าฟังก์ชันนี้จะใช้ได้กับทุก ปลายทางที่เรียกใช้เพื่อดึงข้อมูลไฟล์ JS - ใน Callback ระบบจะแนบ
.gz
ไปกับ URL ของคำขอและตั้งค่าส่วนหัวการตอบกลับContent-Encoding
เป็นgzip
- สุดท้าย
next()
จะช่วยให้มั่นใจว่าลำดับจะดำเนินต่อไปยังการเรียกกลับ ที่อาจเกิดขึ้นถัดไป
เมื่อแอปโหลดซ้ำแล้ว ให้ดูที่แผง Network
อีกครั้ง
เช่นเดียวกับก่อนหน้านี้ เราได้ลดขนาด Bundle ลงอย่างมาก
บทสรุป
Codelab นี้ครอบคลุมกระบวนการลดขนาดและบีบอัดซอร์สโค้ด เทคนิคทั้ง 2 อย่างนี้กำลังกลายเป็นค่าเริ่มต้นในเครื่องมือหลายอย่างที่มีให้บริการในปัจจุบัน ดังนั้นคุณจึงควรตรวจสอบว่าเครื่องมือของคุณรองรับเทคนิคเหล่านี้อยู่แล้วหรือไม่ หรือคุณควรเริ่มใช้กระบวนการทั้ง 2 อย่างด้วยตนเอง