Giảm thiểu và nén tải trọng mạng bằng gzip

Lớp học lập trình này khám phá cách cả việc giảm kích thước và nén gói JavaScript cho ứng dụng sau đây giúp cải thiện hiệu suất trang bằng cách giảm kích thước yêu cầu của ứng dụng.

Ảnh chụp màn hình ứng dụng

Đo

Trước khi tìm hiểu kỹ về cách thêm tính năng tối ưu hoá, bạn nên phân tích trạng thái hiện tại của ứng dụng.

  • Để xem trước trang web, hãy nhấn vào Xem ứng dụng. Sau đó, nhấn vào Toàn màn hình toàn màn hình.

Ứng dụng này cũng được đề cập trong lớp học lập trình "Xoá mã không dùng đến", cho phép bạn bình chọn cho chú mèo con yêu thích của mình. 🐈

Giờ hãy xem kích thước của ứng dụng này:

  1. Nhấn tổ hợp phím "Control + Shift + J" (hoặc "Command+Option+J" trên máy Mac) để mở Công cụ cho nhà phát triển.
  2. Nhấp vào thẻ Mạng.
  3. Chọn hộp kiểm Tắt bộ nhớ đệm.
  4. Tải lại ứng dụng.

Kích thước gói ban đầu trong bảng điều khiển Mạng

Mặc dù đã có rất nhiều tiến trình trong lớp học lập trình "Xoá mã không dùng đến" để giảm kích thước gói này, nhưng 225 KB vẫn còn khá lớn.

Thu nhỏ

Hãy xem xét khối mã sau đây.

function soNice() {
  let counter = 0;

  while (counter < 100) {
    console.log('nice');
    counter++;
  }
}

Nếu hàm này được lưu trong một tệp của riêng nó, thì kích thước tệp sẽ là khoảng 112 B (byte).

Nếu tất cả khoảng trắng bị xoá, mã thu được sẽ có dạng như sau:

function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}

Kích thước tệp hiện tại sẽ là khoảng 83 B. Nếu bị cắt bớt thêm bằng cách giảm độ dài của tên biến và sửa đổi một số biểu thức, mã cuối cùng có thể sẽ có dạng như sau:

function soNice(){for(let i=0;i<100;)console.log("nice"),i++}

Kích thước tệp hiện đạt đến 62 tỷ.

Với mỗi bước, mã ngày càng khó đọc hơn. Tuy nhiên, công cụ JavaScript của trình duyệt diễn giải từng thành phần này theo cùng một cách. Lợi ích của việc làm rối mã nguồn theo cách này có thể giúp giảm kích thước tệp. 112 B thực sự không nhiều để bắt đầu, nhưng vẫn giảm 50% kích thước!

Trong ứng dụng này, webpack phiên bản 4 được dùng làm một gói mô-đun. Bạn có thể xem phiên bản cụ thể trong package.json.

"devDependencies": {
  //...
  "webpack": "^4.16.4",
  //...
}

Theo mặc định, phiên bản 4 đã giảm kích thước gói trong chế độ sản xuất. Công cụ này sử dụng trình bổ trợ TerserWebpackPlugin cho Terser. Terser là một công cụ phổ biến dùng để nén mã JavaScript.

Để biết mã rút gọn trông như thế nào, hãy tiếp tục và nhấp vào main.bundle.js trong khi vẫn ở bảng điều khiển Mạng Công cụ cho nhà phát triển. Giờ hãy nhấp vào thẻ Response (Phản hồi).

Giảm số lượng câu trả lời

Mã ở dạng cuối cùng (rút gọn và cắt bớt) sẽ xuất hiện trong nội dung phản hồi. Để tìm hiểu kích thước của gói nếu không được rút gọn, hãy mở webpack.config.js và cập nhật cấu hình mode.

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

Tải lại ứng dụng và xem lại kích thước gói thông qua bảng điều khiển Network (Mạng) của DevTools

Kích thước gói 767 KB

Đó là một sự khác biệt khá lớn! 😅

Hãy nhớ huỷ các thay đổi tại đây trước khi tiếp tục.

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

Việc đưa vào một quy trình giảm thiểu mã trong ứng dụng phụ thuộc vào các công cụ mà bạn sử dụng:

  • Nếu sử dụng webpack phiên bản 4 trở lên, bạn không cần làm gì thêm vì theo mặc định, mã được rút gọn ở chế độ phát hành chính thức. 👍
  • Nếu bạn sử dụng phiên bản webpack cũ, hãy cài đặt và đưa TerserWebpackPlugin vào quy trình xây dựng gói web. Tài liệu này sẽ giải thích chi tiết về điều này.
  • Các trình bổ trợ rút gọn khác cũng tồn tại và có thể sử dụng thay thế, chẳng hạn như BabelMinifyWebpackPluginClosureCompilerPlugin.
  • Nếu bạn không sử dụng bộ gói mô-đun, hãy sử dụng Terser làm công cụ CLI hoặc trực tiếp đưa công cụ này vào dưới dạng phần phụ thuộc.

Nén

Mặc dù đôi khi, thuật ngữ "nén" được dùng để giải thích cách giảm mã trong quá trình rút gọn, nhưng thực chất thuật ngữ này không được nén theo nghĩa đen.

Nén thường đề cập đến mã đã được sửa đổi bằng thuật toán nén dữ liệu. Không giống như tính năng rút gọn cung cấp mã hoàn toàn hợp lệ, mã nén cần được giải nén trước khi sử dụng.

Với mỗi yêu cầu và phản hồi HTTP, trình duyệt và máy chủ web có thể thêm headers để bao gồm thông tin bổ sung về nội dung đang được tìm nạp hoặc nhận. Bạn có thể xem thông tin này trong thẻ Headers bên trong bảng điều khiển Mạng Công cụ cho nhà phát triển, trong đó có 3 loại hiển thị:

  • Chung biểu thị các tiêu đề chung liên quan đến toàn bộ tương tác phản hồi yêu cầu.
  • Tiêu đề phản hồi cho thấy danh sách các tiêu đề dành riêng cho phản hồi thực tế từ máy chủ.
  • Tiêu đề yêu cầu cho thấy danh sách các tiêu đề mà ứng dụng đính kèm vào yêu cầu.

Hãy xem tiêu đề accept-encoding trong Request Headers.

Chấp nhận tiêu đề mã hoá

accept-encoding được trình duyệt sử dụng để chỉ định những định dạng mã hoá nội dung hay thuật toán nén mà trình duyệt hỗ trợ. Hiện có nhiều thuật toán nén văn bản, nhưng chỉ có 3 thuật toán được hỗ trợ ở đây để nén (và giải nén) các yêu cầu mạng HTTP:

  • Gzip (gzip): Định dạng nén được sử dụng rộng rãi nhất cho các hoạt động tương tác giữa máy chủ và ứng dụng. Công cụ này được xây dựng dựa trên thuật toán Deflate và được hỗ trợ trong mọi trình duyệt hiện tại.
  • Deflate (deflate): Không thường dùng.
  • Brotli (br): Một thuật toán nén mới hơn nhằm cải thiện tỷ lệ nén, nhờ đó có thể tải trang nhanh hơn nữa. Tính năng này được hỗ trợ trong phiên bản mới nhất của hầu hết các trình duyệt.

Ứng dụng mẫu trong hướng dẫn này giống với ứng dụng đã hoàn tất trong lớp học lập trình "Xoá mã không dùng đến", ngoại trừ một thực tế là Express hiện được dùng làm khung máy chủ. Trong các phần tiếp theo, chúng ta sẽ tìm hiểu cả phương thức nén tĩnh và động.

Nén động

Tính năng nén động bao gồm việc nén các thành phần một cách nhanh chóng khi trình duyệt yêu cầu.

Ưu điểm

  • Bạn không cần tạo và cập nhật các phiên bản nén đã lưu của tài sản.
  • Tính năng nén nhanh hoạt động đặc biệt hiệu quả đối với các trang web được tạo động.

Nhược điểm

  • Việc nén tệp ở cấp độ cao hơn để đạt được tỷ lệ nén tốt hơn sẽ mất nhiều thời gian hơn. Điều này có thể dẫn đến lượt truy cập hiệu suất khi người dùng đợi nội dung nén trước khi chúng được gửi bởi máy chủ.

Nén động bằng Node/Express

Tệp server.js chịu trách nhiệm thiết lập máy chủ Nút lưu trữ ứng dụng.

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);
});

Hiện tại, bạn chỉ cần nhập express và sử dụng phần mềm trung gian express.static để tải tất cả tệp HTML, JS và CSS tĩnh trong thư mục public/ (và các tệp đó được tạo bằng gói webpack với mỗi bản dựng).

Để đảm bảo tất cả thành phần đều được nén mỗi khi có yêu cầu, bạn có thể sử dụng thư viện phần mềm trung gian nén. Bắt đầu bằng cách thêm dưới dạng devDependency trong package.json:

"devDependencies": {
  //...
  "compression": "^1.7.3"
},

Sau đó, nhập tệp này vào tệp máy chủ server.js:

const express = require('express');
const compression = require('compression');

Và thêm phần mềm này làm phần mềm trung gian trước khi express.static được gắn kết:

//...

const app = express();

app.use(compression());

app.use(express.static('public'));

//...

Bây giờ, hãy tải lại ứng dụng và xem kích thước gói trong bảng điều khiển Network (Mạng).

Kích thước gói với tính năng nén động

Từ 225 KB đến 61,6 KB! Trong Response Headers hiện tại, tiêu đề content-encoding cho thấy máy chủ đang gửi xuống tệp này được mã hoá bằng gzip.

Tiêu đề mã hoá nội dung

Nén tĩnh

Ý tưởng của phương pháp nén tĩnh là nén và lưu trước các thành phần.

Ưu điểm

  • Độ trễ do mức nén cao không còn là vấn đề đáng lo ngại. Bạn không cần phải thực hiện việc nén tệp một cách nhanh chóng vì giờ đây, bạn có thể tìm nạp trực tiếp các tệp đó.

Nhược điểm

  • Các thành phần cần được nén trong mỗi bản dựng. Thời gian xây dựng có thể tăng đáng kể nếu bạn sử dụng mức nén cao.

Nén tĩnh bằng Nút/Express và webpack

Vì quá trình nén tĩnh liên quan đến việc nén tệp trước thời hạn, nên bạn có thể sửa đổi các chế độ cài đặt gói web để nén thành phần trong bước tạo bản dựng. Bạn có thể sử dụng CompressionPlugin để thực hiện việc này.

Bắt đầu bằng cách thêm dưới dạng devDependency trong package.json:

"devDependencies": {
  //...
  "compression-webpack-plugin": "^1.1.11"
},

Giống như mọi trình bổ trợ webpack khác, hãy nhập trình bổ trợ này vào tệp cấu hình, webpack.config.js:

const path = require("path");

//...

const CompressionPlugin = require("compression-webpack-plugin");

Và đưa mã này vào mảng plugins:

module.exports = {
  //...
  plugins: [
    //...
    new CompressionPlugin()
  ]
}

Theo mặc định, trình bổ trợ này sẽ nén các tệp bản dựng bằng gzip. Hãy xem tài liệu để tìm hiểu cách thêm các tuỳ chọn để sử dụng một thuật toán khác hoặc bao gồm/loại trừ một số tệp nhất định.

Khi ứng dụng tải lại và tạo lại, phiên bản nén của gói chính sẽ được tạo. Mở Glitch Console để xem nội dung bên trong thư mục public/ cuối cùng do máy chủ Nút phân phát.

  • Nhấp vào nút Tools (Công cụ).
  • Nhấp vào nút Bảng điều khiển.
  • Trong bảng điều khiển, hãy chạy các lệnh sau để chuyển sang thư mục public và xem tất cả tệp trong bảng điều khiển đó:
cd public
ls

Các tệp được xuất cuối cùng trong thư mục công khai

Phiên bản đã nén của gói (main.bundle.js.gz) hiện cũng được lưu tại đây. Theo mặc định, CompressionPlugin cũng nén index.html.

Việc tiếp theo cần làm là yêu cầu máy chủ gửi các tệp đã nén này bất cứ khi nào phiên bản JS gốc được yêu cầu. Bạn có thể thực hiện việc này bằng cách xác định một tuyến mới trong server.js trước khi các tệp được phân phát bằng 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 dùng để cho máy chủ biết cách phản hồi yêu cầu GET đối với một điểm cuối cụ thể. Sau đó, hàm callback được dùng để xác định cách xử lý yêu cầu này. Tuyến đường hoạt động như sau:

  • Việc chỉ định '*.js' làm đối số đầu tiên có nghĩa là đối số này hoạt động với mọi điểm cuối được kích hoạt để tìm nạp tệp JS.
  • Trong lệnh gọi lại, .gz được đính kèm vào URL của yêu cầu và tiêu đề phản hồi Content-Encoding được đặt thành gzip.
  • Cuối cùng, next() đảm bảo rằng trình tự sẽ tiếp tục với mọi lệnh gọi lại có thể tiếp theo.

Sau khi ứng dụng tải lại, hãy xem bảng điều khiển Network một lần nữa.

Giảm kích thước gói bằng tính năng nén tĩnh

Giống như trước đây, kích thước gói giảm đáng kể!

Kết luận

Lớp học lập trình này đã đề cập đến quá trình giảm kích thước và nén mã nguồn. Cả hai kỹ thuật này đang trở thành công cụ mặc định trong nhiều công cụ hiện có. Vì vậy, bạn cần tìm hiểu xem chuỗi công cụ của mình đã hỗ trợ các công cụ đó hay chưa hoặc bạn nên bắt đầu tự áp dụng cả hai quy trình.