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

Codelab นี้จะสำรวจว่าทั้งการลดขนาดและการบีบอัดแพ็กเกจ 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 จะลดขนาดแพ็กเกจโดยค่าเริ่มต้นอยู่แล้วในโหมดการใช้งานจริง โดยใช้ 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
  • หากไม่ได้ใช้ 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 ในแผงเครือข่าย

ขนาด 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 กระบวนการด้วยตนเอง