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

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

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

วัดระยะทาง

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

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

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

ต่อไปมาดูกันว่าแอปพลิเคชันนี้มีขนาดใหญ่แค่ไหน

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

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

แม้ว่าโค้ดแล็บ "นำโค้ดที่ไม่ได้ใช้งานออก" จะมีความคืบหน้าไปมากมายเพื่อลดขนาดแพ็กเกจนี้ลง แต่ 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',
  //...

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

ขนาดชุด 767 KB

นับว่าแตกต่างไปค่อนข้างมาก 😅

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

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 (deflate): ไม่นิยมใช้
  • Brotli (br): อัลกอริทึมการบีบอัดแบบใหม่ที่มีเป้าหมายเพื่อปรับปรุงอัตราส่วนการบีบอัดข้อมูลให้ดียิ่งขึ้นซึ่งอาจส่งผลให้หน้าเว็บโหลดได้เร็วขึ้น ซึ่งใช้ได้ในเบราว์เซอร์ส่วนใหญ่ในเวอร์ชันล่าสุด

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

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

การบีบอัดแบบไดนามิกเกี่ยวข้องกับการบีบอัดเนื้อหาอย่างต่อเนื่องตามที่เบราว์เซอร์ขอ

ข้อดี

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

ข้อเสีย

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

การบีบอัดแบบไดนามิกด้วยโหนด/ด่วน

ไฟล์ 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 ลงมา

ส่วนหัวการเข้ารหัสเนื้อหา

การบีบอัดแบบคงที่

แนวคิดที่อยู่เบื้องหลังการบีบอัดแบบคงที่คือให้บีบอัดเนื้อหาไว้และประหยัดเวลาก่อน

ข้อดี

  • ไม่ต้องกังวลเรื่องเวลาในการตอบสนองเนื่องจากระดับการบีบอัดที่สูงอีกต่อไป ไม่จำเป็นต้องบีบอัดไฟล์แบบเรียลไทม์ เพราะดึงข้อมูลโดยตรงได้แล้ว

ข้อเสีย

  • ต้องบีบอัดเนื้อหาในทุกบิลด์ เวลาในการสร้างอาจเพิ่มขึ้นอย่างมากหากใช้ระดับการบีบอัดที่สูง

การบีบอัดแบบคงที่ด้วย 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 ดูเอกสารประกอบเพื่อดูวิธีเพิ่มตัวเลือกเพื่อใช้อัลกอริทึมอื่นหรือรวม/ยกเว้นบางไฟล์

เมื่อแอปโหลดซ้ำและสร้างใหม่ ระบบจะสร้างเวอร์ชันบีบอัดของ 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 สำหรับปลายทางที่เฉพาะเจาะจง จากนั้นจะมีการใช้ฟังก์ชัน Callback เพื่อกำหนดวิธีจัดการคำขอนี้ โดยเส้นทางจะทำงานดังนี้

  • การระบุ '*.js' เป็นอาร์กิวเมนต์แรกหมายความว่าระบบได้ผลกับปลายทางทุกปลายทางที่เริ่มทำงานเพื่อดึงไฟล์ JS
  • ภายใน Callback จะมีการแนบ .gz ไว้กับ URL ของคำขอและตั้งค่าส่วนหัวการตอบกลับ Content-Encoding เป็น gzip
  • สุดท้าย next() จะดูแลให้ลำดับมีการเรียกกลับซึ่งอาจเป็นลำดับถัดไป

เมื่อโหลดแอปซ้ำแล้ว ให้ดูที่แผง Network อีกครั้ง

การลดขนาดกลุ่มด้วยการบีบอัดแบบคงที่

เช่นเดียวกับก่อนหน้านี้ ขนาดแพ็กเกจจะลดลงอย่างมาก

บทสรุป

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