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 sẽ khám phá cách giảm thiểu và nén JavaScript gói cho ứng dụng sau 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 thêm cách tối ưu hoá, bạn nên phân tích trước 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 Toàn màn hình toàn màn hình.

Ứng dụng này, cũng được đề cập trong phần "Xoá ứng dụng không dùng đến mã", cho phép bạn bình chọn cho mèo con. 🐈

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ó nhiều tiến triển trong phần "Xoá mã không dùng đến" lớp học lập trình này để giảm kích thước gói này, 225 KB vẫ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 hàm đó, thì kích thước tệp sẽ nằm trong khoảng 112 tỷ (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 tài khoản bị thu hẹp 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ể kết quả 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, thông tin Công cụ JavaScript diễn giải từng thuật ngữ trong số này theo cùng một cách. Chiến lược phát hành đĩa đơn lợi ích của việc làm rối mã theo cách này có thể giúp thu được tệp nhỏ hơn kích thước. 112 B thực sự không nhiều để bắt đầu, nhưng vẫn là 50% việc giảm kích thước!

Trong ứng dụng này, webpack phiên bản 4 được dùng làm bộ 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. Chiến dịch này sử dụng TerserWebpackPlugin một trình bổ trợ cho Terser. Terser là một công cụ phổ biến dùng để nén mã JavaScript.

Để biết mã được 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ủa Công cụ cho nhà phát triển. Giờ hãy nhấp vào Thẻ 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 xem gói có thể lớn đến mức nào nếu không được giảm kích thước, hãy mở webpack.config.js rồi 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 Mạng Công cụ cho nhà phát triển

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ông cụ mà bạn sử dụng:

  • Nếu sử dụng webpack phiên bản 4 trở lên, thì bạn không cần làm gì thêm vì theo mặc định, mã được giảm thiểu ở chế độ phát hành chính thức. 👍
  • Nếu bạn đang dùng phiên bản webpack cũ, hãy cài đặt và thêm TerserWebpackPlugin vào quy trình xây dựng webpack. Tài liệu 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 không sử dụng bộ gói mô-đun, hãy sử dụng Terser dưới dạng 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ù thuật ngữ "nén" đôi khi được dùng thường xuyên để giải thích cách mã được giảm trong quá trình rút gọn, nó không thực sự được nén trong nghĩa đen.

Nén thường đề cập đến mã đã được sửa đổi bằng cách sử dụng một dữ liệu thuật toán nén. Không giống như việc rút gọn, kết thúc bằng việc rút gọn mã 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 tiêu đề cần bao gồm thông tin bổ sung về nội dung được tìm nạp hoặc nhận. Thông tin này có thể là bạn nhìn thấy trong thẻ Headers trong bảng điều khiển Mạng Công cụ cho nhà phát triển, trong đó có 3 loại được hiển thị:

  • General (Chung) biểu thị các tiêu đề chung liên quan đến toàn bộ phản hồi yêu cầu tương tác.
  • 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 hiển thị danh sách các tiêu đề được đính kèm với yêu cầu bằng khách hàng.

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 nội dung nào định dạng mã hoá hoặc thuật toán nén mà Google hỗ trợ. Có rất nhiều khác nhau, nhưng chỉ có ba thuật toán nén được hỗ trợ tại đâ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 phổ biến nhất cho các tương tác giữa máy chủ và máy khách. Trò chơi này được xây dựng dựa trê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 phiên bản nén mới hơn nhằm cải thiện hơn nữa tỷ lệ nén, mà có thể dẫn đến tải trang nhanh hơn nữa. Định dạ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 được hoàn tất trong Lớp học lập trình "Xoá mã không dùng đến", trừ một thực tế là Express hiện được dùng làm khung máy chủ. Trong thời gian tới có một số phần, cả nén tĩnh và động sẽ được tìm hiểu.

Nén động

Tính năng nén động giúp nén tài sản một cách nhanh chóng khi chúng nhận mà trình duyệt yêu cầu.

Ưu điểm

  • Bạn không cần phải tạo và cập nhật các phiên bản nén đã lưu của tài sản xong.
  • 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

  • Nén tệp ở mức cao hơn để đạt được tỷ lệ nén tốt hơn 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 chờ 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 express.static phần mềm trung gian để tải tất cả các tệp HTML, JS và CSS tĩnh trong Thư mục public/ (và các tệp đó do webpack tạo với mỗi bản dựng).

Để đảm bảo tất cả các thành phần đều được nén mỗi khi có yêu cầu, nén thư viện phần mềm trung gian có thể để sử dụng. 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! Hiện tại, trong Response Headers, một content-encoding tiêu đề 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 đằng sau phương pháp nén tĩnh là nén và lưu tài sản sớm hơ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 lên đáng kể nếu 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 các tệp trước, nên gói webpack có thể sửa đổi được chế độ cài đặt để nén nội dung trong bước tạo. CompressionPlugin có thể được sử dụng 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 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. Xem qua tại 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ẽ hiện đã được tạo. Mở Glitch Console để xem 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 để thay đổi thành public và xem tất cả các tệp trong thư mục đó:
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 lưu tại đây dưới dạng tốt. Theo mặc định, CompressionPlugin cũng nén index.html.

Việc tiếp theo cần thực hiện 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ủa chúng được yêu cầu. Có thể làm được bằng cách xác định một tuyến mới trong server.js trước khi tệp được phân phát 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ểm cuối cụ thể. Sau đó, hàm callback được dùng để xác định cách xử lý trường hợp này của bạn. 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ẽ 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ự tiếp tục với bất kỳ lệnh gọi lại nào 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 mặc định trong nhiều công cụ hiện nay, do đó, điều quan trọng là phải tìm hiểu xem chuỗi công cụ của bạn đã hoặc hỗ trợ các quy trình đó hay bạn nên tự mình bắt đầu áp dụng cả hai quy trình.