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 ของเบราว์เซอร์จะตีความแท็กเหล่านี้แต่ละตัวในลักษณะเดียวกัน ข้อดีของการสร้างโค้ดที่ปรับให้ยากต่อการอ่าน (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',
//...
โหลดแอปพลิเคชันซ้ำและดูขนาด Bundle อีกครั้งผ่านแผงเครือข่ายของเครื่องมือสำหรับนักพัฒนาเว็บ
นั่นสร้างความแตกต่างได้มากทีเดียว 😅
อย่าลืมเปลี่ยนกลับการเปลี่ยนแปลงที่นี่ก่อนดำเนินการต่อ
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
): ไม่นิยมใช้ - Brotli (
br
): อัลกอริทึมการบีบอัดแบบใหม่ที่มีเป้าหมายเพื่อปรับปรุงอัตราส่วนการบีบอัดให้ดียิ่งขึ้น ซึ่งอาจส่งผลให้โหลดหน้าเว็บได้เร็วขึ้น ฟีเจอร์นี้ใช้ได้ในเวอร์ชันล่าสุดของเบราว์เซอร์ส่วนใหญ่
แอปพลิเคชันตัวอย่างในบทแนะนำนี้เหมือนกับแอปที่เสร็จสมบูรณ์ใน Codelab "นำโค้ดที่ไม่ได้ใช้ออก" ยกเว้นว่าตอนนี้มีการใช้ Express เป็นเฟรมเวิร์กของเซิร์ฟเวอร์แล้ว ใน 2-3 ส่วนถัดไป เราจะสำรวจการบีบอัดทั้งแบบคงที่และแบบไดนามิก
การบีบอัดแบบไดนามิก
การบีบอัดไดนามิกเกี่ยวข้องกับการบีบอัดเนื้อหาในทันทีในขณะที่เบราว์เซอร์ร้องขอ
ข้อดี
- คุณไม่จำเป็นต้องสร้างและอัปเดตเนื้อหาเวอร์ชันบีบอัดที่บันทึกไว้
- การบีบอัดอย่างรวดเร็วเหมาะสำหรับหน้าเว็บที่สร้างขึ้นแบบไดนามิก
ข้อเสีย
- การบีบอัดไฟล์ในระดับที่สูงขึ้นเพื่อให้ได้อัตราส่วนการบีบอัดที่ดียิ่งขึ้นจะใช้เวลานานขึ้น ซึ่งอาจทำให้เกิด Hit ด้านประสิทธิภาพเนื่องจากผู้ใช้รอให้เนื้อหาบีบอัดก่อนที่เซิร์ฟเวอร์จะส่งเนื้อหา
การบีบอัดแบบไดนามิกด้วยโหนด/ด่วน
ไฟล์ 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
การบีบอัดแบบคงที่
แนวคิดในการบีบอัดแบบคงที่คือการบีบอัดเนื้อหาและประหยัดเวลาไว้ล่วงหน้า
ข้อดี
- คุณจึงไม่ต้องกังวลเรื่องเวลาในการตอบสนองเนื่องจากระดับการบีบอัดในระดับสูงอีกต่อไป การบีบอัดไฟล์ไม่จำเป็นต้องดำเนินการในทันที เนื่องจากสามารถดึงข้อมูลได้โดยตรงแล้ว
ข้อเสีย
- ต้องบีบอัดเนื้อหาพร้อมกับบิลด์ทั้งหมด เวลาบิลด์อาจเพิ่มขึ้นอย่างมากหากใช้ระดับการบีบอัดสูง
การบีบอัดแบบคงที่ด้วยโหนด/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 สำหรับปลายทางหนึ่งๆ จากนั้นระบบจะใช้ฟังก์ชันเรียกกลับเพื่อกำหนดวิธีจัดการคำขอนี้ เส้นทางมีการทำงานดังนี้
- การระบุ
'*.js'
เป็นอาร์กิวเมนต์แรกหมายความว่าวิธีนี้ได้ผลกับทุกปลายทางที่เริ่มทำงานเพื่อดึงข้อมูลไฟล์ JS - ภายในโค้ดเรียกกลับ ระบบจะแนบ
.gz
กับ URL ของคำขอและตั้งค่าส่วนหัวการตอบกลับContent-Encoding
เป็นgzip
- สุดท้าย
next()
จะดูแลให้ลำดับยังคงมีอยู่ในโค้ดเรียกกลับซึ่งอาจเป็นลำดับถัดไป
เมื่อแอปโหลดซ้ำแล้ว ให้ดูที่แผง Network
อีกครั้ง
และเช่นเคยคือขนาดกลุ่มที่ลดลงอย่างมาก
บทสรุป
Codelab นี้ครอบคลุมกระบวนการลดขนาดและบีบอัดซอร์สโค้ด ทั้ง 2 เทคนิคนี้เป็นค่าเริ่มต้นในเครื่องมือจำนวนมากที่มีให้บริการในปัจจุบัน ดังนั้นคุณจึงควรต้องตรวจสอบว่า Toolchain รองรับอยู่แล้วหรือไม่ หรือคุณควรเริ่มใช้ทั้ง 2 กระบวนการด้วยตนเอง