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.
Đ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 .
Ứ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:
- 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.
- Nhấp vào thẻ Mạng.
- Chọn hộp kiểm Tắt bộ nhớ đệm.
- Tải lại ứng dụ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).
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
Đó 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ư BabelMinifyWebpackPlugin và ClosureCompilerPlugin.
- 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
.
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).
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
.
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
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ồiContent-Encoding
được đặt thànhgzip
. - 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ố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.