ลดขนาดและบีบอัดเพย์โหลดของเครือข่ายด้วย 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 ของเบราว์เซอร์จะตีความแท็กเหล่านี้แต่ละตัวในลักษณะเดียวกัน ข้อดีของการสร้างโค้ดที่ปรับให้ยากต่อการอ่าน (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 อีกครั้งผ่านแผงเครือข่ายของเครื่องมือสำหรับนักพัฒนาเว็บ

ขนาดแพ็กเกจ 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): ไม่นิยมใช้
  • 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 กระบวนการด้วยตนเอง