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 đều 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 đi sâu vào việc 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 View App (Xem ứng dụng), sau đó nhấn vào Fullscreen 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 mà bạn yêu thích. 🐈

Bây giờ, hãy xem ứng dụng này lớn đến mức nào:

  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ó tiến bộ 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 là khá lớn.

Giảm thiểu

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

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 riêng, thì kích thước tệp sẽ vào khoảng 112 B (byte).

Nếu bạn xoá tất cả khoảng trắng, mã kết quả sẽ có dạng như sau:

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

Kích thước tệp lúc này sẽ là khoảng 83 B. Nếu nó tiếp tục bị hỏng 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 62 B.

Qua từng bước, mã sẽ trở nên khó đọc hơn. Tuy nhiên, công cụ JavaScript của trình duyệt diễn giải từng loại 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 đạt được kích thước tệp nhỏ hơn. 112 B thực sự không có nhiều khi bắt đầu, nhưng vẫn giảm được 50% kích thước!

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

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

Phiên bản 4 đã giảm kích thước gói theo mặc định trong chế độ phát hành chính thức. Sử dụng một trình bổ trợ TerserWebpackPlugin cho Terser. Terser là một công cụ phổ biến được sử dụng để nén mã JavaScript.

Để hình dung được 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 ở trong 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).

Phản hồi giảm thiểu

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 xem gói có thể lớn bao nhiêu nếu không được giảm kích thước, 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 là 767 KB

Có 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 quy trình giảm kích thước mã vào ứng dụng sẽ phụ thuộc vào các công cụ mà bạn sử dụng:

  • Nếu sử dụng gói webpack v4 trở lên, thì bạn không cần làm gì thêm vì mã sẽ được giảm kích thước theo mặc định ở chế độ phát hành chính thức. 👍
  • Nếu bạn sử dụng phiên bản gói web cũ hơn, 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 giải thích chi tiết về vấn đề này.
  • Các trình bổ trợ giảm thiểu khác cũng tồn tại và bạn có thể dùng các trình bổ trợ này, chẳng hạn như BabelMinifyWebpackPluginClosureCompilerPlugin.
  • Nếu bạn hoàn toàn không dùng trình đóng gói mô-đun, hãy sử dụng Terser làm công cụ CLI hoặc đưa trực tiếp mô-đun này vào dưới dạng phần phụ thuộc.

Nén

Mặc dù thuật ngữ "nén" đôi khi được dùng một cách thoải mái để giải thích cách giảm bớt mã trong quá trình rút gọn, nhưng thực ra 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ư phương thức giảm thiểu (sẽ cung cấp mã hoàn toàn hợp lệ), bạn cần giải nén mã 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 trên thẻ Headers trong bảng điều khiển Mạng cho nhà phát triển, nơi hiển thị 3 loại:

  • General (Chung) biểu thị cho 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 đề cụ thể cho phản hồi thực tế từ máy chủ.
  • Request Headers (Tiêu đề yêu cầu) cho thấy danh sách tiêu đề được ứ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 hoặc thuật toán nén mà trình duyệt hỗ trợ. Có nhiều thuật toán nén văn bản trên thị trường, nhưng chỉ có ba 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à máy khách. API này được xây dựng dựa trên thuật toán Deflate và được hỗ trợ trong tất cả các 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 các 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 thành trong lớp học lập trình "Xoá mã không dùng đến", ngoại trừ việc Express hiện được dùng làm khung máy chủ. Trong các phần tiếp theo, bạn sẽ tìm hiểu về cả phương thức nén tĩnh và nén động.

Né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 theo yêu cầu của trình duyệt.

Ưu điểm

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

Nhược điểm

  • Việc nén các tệp ở cấp cao hơn để đạt 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 kết quả về hiệu suất khi người dùng chờ các thành phần nén trước khi được máy chủ gửi đi.

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ả các tệp HTML, JS và CSS tĩnh trong thư mục public/ (và những tệp đó sẽ được tạo theo gói web cho 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 nó vào tệp máy chủ, server.js:

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

Và thêm nó làm phần mềm trung gian trước khi express.static được liê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 có tính năng nén động

Từ 225 KB sang 61,6 KB! Trong Response Headers hiện tại, tiêu đề content-encoding cho biết máy chủ đang gửi 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à tiết kiệm trước các tài sản.

Ưu điểm

  • Độ trễ do mức nén cao không còn là mối lo ngại nữa. Bạn không cần phải 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 này.

Nhược điểm

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

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

Vì phương pháp nén tĩnh bao gồm việc nén tệp trước, nên bạn có thể sửa đổi các chế độ cài đặt gói web để nén các thành phần trong bước tạo bản dựng. Bạn có thể sử dụng CompressionPlugin cho 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 vào mảng plugins:

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

Theo mặc định, trình bổ trợ này 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 Công cụ.
  • Hãy 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 để thay đổi thành thư mục public và xem tất cả các tệp trong thư mục đó:
cd public
ls

Các tệp đã 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 bạn 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 ban đầu đượ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 cho 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 sẽ phù hợp 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ự này sẽ tiếp tục mọi lệnh gọi lại có thể xảy ra tiếp theo.

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

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

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

Kết luận

Lớp học lập trình này đề cập đến quy trình giảm thiểu và nén mã nguồn. Cả hai kỹ thuật này đang trở thành phương thứ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 bạn đã hỗ trợ những kỹ thuật này hay chưa hoặc liệu bạn có nên bắt đầu tự áp dụng cả hai quy trình hay không.