ลดขนาดและบีบอัดเพย์โหลดของเครือข่ายด้วย gzip

โค้ดแล็บนี้จะอธิบายว่าการทำให้โค้ด JavaScript เล็กลงและการบีบอัดแพ็กเกจ JavaScript สําหรับแอปพลิเคชันต่อไปนี้ช่วยปรับปรุงประสิทธิภาพหน้าเว็บได้อย่างไรด้วยการลดขนาดคําขอของแอป

ภาพหน้าจอแอป

วัดระยะทาง

ก่อนเพิ่มการเพิ่มประสิทธิภาพ คุณควรวิเคราะห์สถานะปัจจุบันของแอปพลิเคชันก่อน

  • หากต้องการดูตัวอย่างเว็บไซต์ ให้กดดูแอป แล้วกดเต็มหน้าจอ เต็มหน้าจอ

แอปนี้ซึ่งอยู่ใน Codelab "นำโค้ดที่ไม่ได้ใช้ออก" ให้คุณโหวตให้กับลูกแมวตัวโปรดได้ 🐈

มาดูขนาดของแอปพลิเคชันนี้กัน

  1. กดแป้น Control+Shift+J (หรือ Command+Option+J ใน Mac) เพื่อเปิดเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์
  2. คลิกแท็บเครือข่าย
  3. เลือกช่องทำเครื่องหมายปิดใช้แคช
  4. โหลดแอปซ้ำ

ขนาดกลุ่มเดิมในแผงเครือข่าย

แม้ว่าจะมีความคืบหน้าอย่างมากใน"นำโค้ดที่ไม่ได้ใช้ออก" ทาง 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 จะย่อขนาด Bundle โดยค่าเริ่มต้นอยู่แล้วในโหมดเวอร์ชันที่ใช้งานจริง โดยใช้ TerserWebpackPluginปลั๊กอินสำหรับ Terser Terser เป็นเครื่องมือยอดนิยมที่ใช้บีบอัดโค้ด JavaScript

หากต้องการดูว่าโค้ดที่บีบอัดแล้วมีลักษณะเป็นอย่างไร ให้คลิก main.bundle.js ขณะยังอยู่ในแผงเครือข่ายของ DevTools จากนั้นคลิกแท็บคำตอบ

การตอบกลับแบบมินิมอล

โค้ดในรูปแบบสุดท้ายซึ่งได้รับการย่อขนาดและทำให้อ่านไม่ออกจะแสดงอยู่ในเนื้อหาของคำตอบ หากต้องการดูขนาดที่เป็นไปได้ของ Bundle หากไม่ได้มีการย่อขนาด ให้เปิด webpack.config.js แล้วอัปเดตการกําหนดค่า mode

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

โหลดแอปพลิเคชันอีกครั้งและดูขนาด Bundle อีกครั้งผ่านแผงเครือข่ายของเครื่องมือสำหรับนักพัฒนาเว็บ

ขนาด Bundle 767 KB

ความแตกต่างนี้ค่อนข้างมาก 😅

โปรดเปลี่ยนกลับการเปลี่ยนแปลงที่นี่ก่อนดำเนินการต่อ

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

การรวมกระบวนการย่อโค้ดในแอปพลิเคชันจะขึ้นอยู่กับเครื่องมือที่คุณใช้

  • หากใช้ webpack v4 ขึ้นไป ก็ไม่ต้องดำเนินการใดๆ เพิ่มเติมเนื่องจากระบบจะย่อโค้ดโดยค่าเริ่มต้นในโหมดเวอร์ชันที่ใช้งานจริง 👍
  • หากใช้ webpack เวอร์ชันเก่า ให้ติดตั้งและรวม TerserWebpackPlugin ไว้ในกระบวนการบิลด์ webpack เอกสารประกอบอธิบายเรื่องนี้อย่างละเอียด
  • นอกจากนี้ยังมีปลั๊กอินอื่นๆ สำหรับการลดขนาดที่ใช้แทนได้ เช่น BabelMinifyWebpackPlugin และ ClosureCompilerPlugin
  • หากไม่ได้ใช้เครื่องมือจัดกลุ่มโมดูลเลย ให้ใช้ Terser เป็นเครื่องมือ CLI หรือรวมเป็นข้อกําหนดโดยตรง

การบีบอัด

แม้ว่าบางครั้งเราจะใช้คำว่า "การบีบอัด" อย่างหลวมๆ เพื่ออธิบายวิธีลดโค้ดในระหว่างกระบวนการ Minify แต่จริงๆ แล้วไม่ได้มีการบีบอัดในความหมายที่แท้จริง

การบีบอัดมักหมายถึงโค้ดที่ได้รับการแก้ไขโดยใช้อัลกอริทึมการบีบอัดข้อมูล ซึ่งแตกต่างจากการทำให้ไฟล์เล็กลงที่ให้โค้ดที่ถูกต้องสมบูรณ์ แต่ต้องคลายการบีบอัดโค้ดก่อนใช้งาน

เมื่อมีการขอและตอบกลับ HTTP ทุกครั้ง เบราว์เซอร์และเว็บเซิร์ฟเวอร์จะเพิ่มส่วนหัวเพื่อรวมข้อมูลเพิ่มเติมเกี่ยวกับชิ้นงานที่ดึงข้อมูลหรือได้รับ ซึ่งจะดูได้ในแท็บ Headers ภายในแผงเครือข่ายของ DevTools ซึ่งจะแสดง 3 ประเภทดังนี้

  • ทั่วไปแสดงส่วนหัวทั่วไปที่เกี่ยวข้องกับการโต้ตอบแบบคำขอ-การตอบกลับทั้งหมด
  • ส่วนหัวการตอบกลับแสดงรายการส่วนหัวเฉพาะสำหรับการตอบกลับจริงจากเซิร์ฟเวอร์
  • ส่วนหัวของคำขอแสดงรายการส่วนหัวที่ไคลเอ็นต์แนบมากับคำขอ

ดูส่วนหัว accept-encoding ใน Request Headers

ส่วนหัว Accept-Encoding

accept-encoding ใช้โดยเบราว์เซอร์เพื่อระบุรูปแบบการเข้ารหัสเนื้อหาหรืออัลกอริทึมการบีบอัดที่รองรับ อัลกอริทึมการบีบอัดข้อความมีอยู่หลายรายการ แต่มีเพียง 3 รายการที่รองรับการบีบอัด (และการขยายข้อมูลที่บีบอัด) ของคําขอเครือข่าย HTTP ดังนี้

  • Gzip (gzip): รูปแบบการบีบอัดที่ใช้กันอย่างแพร่หลายที่สุดสำหรับการโต้ตอบระหว่างเซิร์ฟเวอร์กับไคลเอ็นต์ ซึ่งพัฒนาต่อยอดมาจากอัลกอริทึม Deflate และเบราว์เซอร์ทุกรุ่นในปัจจุบันรองรับ
  • Deflate (deflate): ไม่ค่อยได้ใช้
  • Brotli (br): อัลกอริทึมการบีบอัดที่ใหม่กว่าซึ่งมุ่งปรับปรุงอัตราส่วนการบีบอัดให้ดียิ่งขึ้น ซึ่งอาจส่งผลให้หน้าเว็บโหลดได้เร็วขึ้น ฟีเจอร์นี้ใช้ได้ในเบราว์เซอร์ส่วนใหญ่เวอร์ชันล่าสุด

แอปพลิเคชันตัวอย่างในบทแนะนำนี้เหมือนกับแอปที่เสร็จสมบูรณ์ใน Codelab "นำโค้ดที่ไม่ได้ใช้ออก" ยกเว้นตอนนี้ใช้ Express เป็นเฟรมเวิร์กเซิร์ฟเวอร์ ส่วนถัดไปจะกล่าวถึงทั้งการบีบอัดแบบคงที่และแบบไดนามิก

การบีบอัดแบบไดนามิก

การบีบอัดแบบไดนามิกเกี่ยวข้องกับการบีบอัดชิ้นงานขณะที่เบราว์เซอร์ขอ

ข้อดี

  • คุณไม่จำเป็นต้องสร้างและอัปเดตชิ้นงานเวอร์ชันที่บีบอัดไว้
  • การบีบอัดขณะใช้งานเหมาะอย่างยิ่งสําหรับหน้าเว็บที่สร้างขึ้นแบบไดนามิก

ข้อเสีย

  • การบีบอัดไฟล์ในระดับที่สูงขึ้นเพื่อให้ได้อัตราส่วนการบีบอัดที่ดีขึ้นจะใช้เวลานานกว่า ซึ่งอาจส่งผลเสียต่อประสิทธิภาพเนื่องจากผู้ใช้ต้องรอให้เซิร์ฟเวอร์บีบอัดชิ้นงานก่อนที่จะส่ง

การบีบอัดแบบไดนามิกด้วย 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 ในแผงเครือข่าย

ขนาด 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

ไฟล์เอาต์พุตขั้นสุดท้ายในไดเรกทอรีสาธารณะ

ระบบจะบันทึก 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 ด้วยการบีบอัดแบบคงที่

เช่นเดียวกับก่อนหน้านี้ ขนาดของ Bundle ลดลงอย่างมาก

บทสรุป

Codelab นี้ครอบคลุมกระบวนการลดขนาดและบีบอัดซอร์สโค้ด เทคนิคทั้ง 2 แบบนี้กำลังกลายเป็นค่าเริ่มต้นในเครื่องมือจํานวนมากที่มีให้บริการในปัจจุบัน คุณจึงควรตรวจสอบว่าเครื่องมือทางเทคนิคของคุณรองรับเทคนิคเหล่านี้อยู่แล้ว หรือคุณควรเริ่มใช้ทั้ง 2 กระบวนการด้วยตนเอง