Lớp học lập trình này khám phá cách cả việc giảm thiểu 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 lường
Trước khi bắt đầu thêm các hoạt độ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, rồi 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 mà bạn yêu thích. 🐈
Bây 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ở DevTools.
- Nhấp vào thẻ Mạng.
- Chọn hộp đánh dấu Tắt bộ nhớ đệm.
- Tải lại ứng dụng.
Mặc dù đã có nhiều 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à một kích thước khá lớn.
Giảm kích thước
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 hiện sẽ là khoảng 83 B. Nếu mã này bị xáo trộn 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, thì mã cuối cùng có thể trô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.
Với mỗi bước, mã sẽ trở nên khó đọc hơn. Tuy nhiên, công cụ JavaScript của trình duyệt sẽ diễn giải từng loại theo cùng một cách. Lợi ích của việc làm rối mã 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, nhưng kích thước vẫn giảm được 50%!
Trong ứng dụng này, webpack phiên bản 4 được dùng làm trình kết hợp 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 thiểu gói trong chế độ phát hành công khai. Nó sử dụng TerserWebpackPlugin
một trình bổ trợ cho Terser.
Terser là một công cụ phổ biến được dùng để nén mã JavaScript.
Để biết mã được rút gọn trông như thế nào, hãy 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. Bây giờ, hãy nhấp vào thẻ Câu trả lời.
Mã ở dạng cuối cùng, được rút gọn và làm rối mã, sẽ xuất hiện trong nội dung phản hồi.
Để biết kích thước của gói nếu không được rút gọn, 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ủa Công cụ cho nhà phát triển
Đó là một sự khác biệt khá lớn! 😅
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 thiểu mã vào ứng dụng của bạn phụ thuộc vào các công cụ mà bạn sử dụng:
- Nếu bạn sử dụng webpack phiên bản 4 trở lên, bạn không cần làm gì thêm vì mã được giảm thiểu theo mặc định ở chế độ phát hành chính thức. 👍
- Nếu bạn dùng phiên bản webpack cũ, hãy cài đặt và đưa
TerserWebpackPlugin
vào quy trình tạo webpack. Tài liệu giải thích chi tiết về vấn đề này. - Ngoài ra, còn có những trình bổ trợ giảm kích thước khác mà bạn có thể dùng thay thế, chẳng hạn như BabelMinifyWebpackPlugin và ClosureCompilerPlugin.
- Nếu bạn không sử dụng trình kết hợp mô-đun, hãy dùng Terser làm công cụ CLI hoặc đưa trực tiếp vào dưới dạng một phần phụ thuộc.
Nén
Mặc dù đôi khi thuật ngữ "nén" được dùng một cách không chính xác để giải thích cách mã giảm trong quá trình rút gọn, nhưng mã không thực sự đượ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ư việc giảm thiểu giúp cung cấp mã hoàn toàn hợp lệ, mã nén cần được giải nén trước khi được 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 đề để đưa thêm thông tin về tài sản đang được tìm nạp hoặc nhận. Bạn có thể thấy điều này trong thẻ Headers
trong bảng điều khiển Mạng của Công cụ cho nhà phát triển, nơi có 3 loại được hiển thị:
- Chung đại diện cho các tiêu đề chung liên quan đến toàn bộ hoạt động tương tác yêu cầu-phản hồi.
- 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 biết danh sách tiêu đề được ứng dụng khách đí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 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, 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à máy khách. Thuật toán 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 xuyên được sử dụng. - Brotli (
br
): Một thuật toán nén mới hơn nhằm cải thiện hơn nữa tỷ lệ nén, có thể giúp trang tải 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 hệt 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 một vài phần tiếp theo, chúng ta sẽ khám phá cả phương pháp nén tĩnh và nén động.
Nén động
Quy trình nén động bao gồm việc nén các thành phần ngay lập tức 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.
- Việc nén tức thì đặc biệt hiệu quả đối với những 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ể gây ảnh hưởng đến hiệu suất vì người dùng phải đợi các thành phần nén trước khi được máy chủ gử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ủ Node 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, tất cả những gì bạn cần làm là nhập express
và sử dụng express.static
middleware để 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 đó được webpack tạo ra trong 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, 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 thành phần này dưới dạng devDependency
trong package.json
:
"devDependencies": {
//...
"compression": "^1.7.3"
},
Và 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 nó làm phần mềm trung gian trước khi express.static
được gắn:
//...
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 Mạng.
Từ 225 KB xuống 61,6 KB! Trong Response Headers
, tiêu đề content-encoding
cho biết máy chủ đang gửi tệp này được mã hoá bằng gzip
.
Nén tĩnh
Ý tưởng đằng sau việc nén tĩnh là nén và lưu tài sản trước thời hạn.
Ưu điểm
- Độ trễ do mức độ nén cao không còn là vấn đề nữa. Không cần phải nén các tệp ngay lập tức vì giờ đây, bạn có thể tìm nạp trực tiếp các tệp đó.
Nhược điểm
- Bạn cần nén các thành phần bằng mọi bản dựng. Thời gian tạo có thể tăng lên đáng kể nếu bạn sử dụng các mức nén cao.
Nén tĩnh bằng Node/Express và webpack
Vì tính năng 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 chế độ cài đặt webpack để nén tài sản trong bước tạo.
Bạn có thể dùng CompressionPlugin
cho việc này.
Bắt đầu bằng cách thêm thành phần này 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 nó vào mảng plugins
:
module.exports = {
//...
plugins: [
//...
new CompressionPlugin()
]
}
Theo mặc định, trình bổ trợ 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 lựa chọn sử dụng một thuật toán khác hoặc đưa vào/loại trừ một số tệp nhất định.
Khi ứng dụng tải lại và xây dựng lại, một phiên bản nén của gói chính sẽ được tạo. Mở Bảng điều khiển Glitch để xem nội dung bên trong thư mục public/
cuối cùng do máy chủ Node cung cấp.
- Nhấp vào nút 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ả các tệp của thư mục này:
cd public
ls
Phiên bản nén gzip của gói, main.bundle.js.gz
, 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 được nén bằng gzip này bất cứ khi nào các phiên bản JS gốc của chúng đượ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 đường 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
được 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 đó, một hàm callback sẽ đượ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ều này sẽ hoạt động cho mọi điểm cuối được kích hoạt để tìm nạp một 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 chuỗi tiếp tục đến mọi lệnh gọi lại có thể xuất hiện tiếp theo.
Sau khi ứng dụng tải lại, hãy xem lại bảng điều khiển Network
.
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 quy trình giảm thiểu 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 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 kỹ thuật này hay chưa hoặc liệu bạn có nên tự bắt đầu áp dụng cả hai quy trình này hay không.