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

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

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

วัดผล

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

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

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

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

  1. กด `Control+Shift+J` (หรือ `Command+Option+J` ใน Mac) เพื่อเปิด DevTools
  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 ไบต์ หากมีการดัดแปลงเพิ่มเติมโดยการลด ความยาวของชื่อตัวแปรและแก้ไขนิพจน์บางส่วน โค้ดสุดท้ายอาจ มีลักษณะดังนี้

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 หรือรวมเป็นทรัพยากร Dependency โดยตรง

การบีบอัด

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

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

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

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

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

ส่วนหัว Accept-Encoding

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

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

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

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

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

ข้อดี

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

ข้อเสีย

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

การบีบอัดแบบไดนามิกด้วย 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 เวอร์ชันที่บีบอัดด้วย gzip, main.bundle.js.gz ไว้ที่นี่ด้วยแล้ว 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
  • ใน Callback ระบบจะแนบ .gz ไปกับ URL ของคำขอและตั้งค่าส่วนหัวการตอบกลับ Content-Encoding เป็น gzip
  • สุดท้าย next() จะช่วยให้มั่นใจว่าลำดับจะดำเนินต่อไปยังการเรียกกลับ ที่อาจเกิดขึ้นถัดไป

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

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

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

บทสรุป

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