Giảm tải JavaScript bằng cách phân tách mã

Hầu hết các trang web và ứng dụng đều được tạo thành từ nhiều phần khác nhau. Thay vì gửi tất cả JavaScript tạo nên ứng dụng ngay khi tải trang đầu tiên, việc phân tách JavaScript thành nhiều phần sẽ cải thiện hiệu suất trang.

Lớp học lập trình này cho thấy cách sử dụng tính năng phân tách mã để cải thiện hiệu suất của một ứng dụng đơn giản giúp sắp xếp ba số.

Cửa sổ trình duyệt hiển thị một ứng dụng có tên Magic Sorter (Trình sắp xếp ma thuật) với 3 trường để nhập số và một nút sắp xếp.

Đo

Như mọi khi, trước tiên, bạn cần đo lường hiệu suất của trang web trước khi cố gắng thêm bất kỳ tính năng tối ưu hoá nào.

  1. Để xem trước trang web, hãy nhấn vào Xem ứng dụng. Sau đó, nhấn vào biểu tượng Màn hình toàn cảnh toàn màn hình.
  2. Nhấn tổ hợp phím `Ctrl+Shift+J` (hoặc `Command+Option+J` trên máy Mac) để mở DevTools.
  3. Nhấp vào thẻ Mạng.
  4. Chọn hộp đánh dấu Tắt bộ nhớ đệm.
  5. Tải lại ứng dụng.

Bảng điều khiển Mạng hiển thị gói JavaScript 71,2 KB.

71,2 KB JavaScript chỉ để sắp xếp một vài con số trong một ứng dụng đơn giản. What gives?

Trong mã nguồn (src/index.js), thư viện lodash được nhập và sử dụng trong ứng dụng này. Lodash cung cấp nhiều hàm tiện ích hữu ích, nhưng chỉ có một phương thức duy nhất trong gói này được sử dụng ở đây. Một lỗi phổ biến thường gặp khi cài đặt và nhập toàn bộ các phần phụ thuộc bên thứ ba nếu chỉ sử dụng một phần nhỏ.

Tối ưu hoá

Có một số cách để giảm kích thước gói:

  1. Viết một phương thức sắp xếp tuỳ chỉnh thay vì nhập thư viện bên thứ ba
  2. Sử dụng phương thức Array.prototype.sort() tích hợp sẵn để sắp xếp theo số
  3. Chỉ nhập phương thức sortBy từ lodash chứ không phải toàn bộ thư viện
  4. Chỉ tải mã để sắp xếp khi người dùng nhấp vào nút

Lựa chọn 1 và 2 là các phương pháp hoàn toàn phù hợp để giảm kích thước gói (và có thể phù hợp nhất với một ứng dụng thực tế). Tuy nhiên, những nội dung đó không được sử dụng trong hướng dẫn này nhằm mục đích giảng dạy 😈.

Cả tuỳ chọn 3 và 4 đều giúp cải thiện hiệu suất của ứng dụng này. Các phần tiếp theo của lớp học lập trình này sẽ trình bày các bước này. Giống như mọi hướng dẫn lập trình khác, hãy luôn cố gắng tự viết mã thay vì sao chép và dán.

Chỉ nhập những gì bạn cần

Bạn cần sửa đổi một vài tệp để chỉ nhập một phương thức từ lodash. Để bắt đầu, hãy thay thế phần phụ thuộc này trong package.json:

"lodash": "^4.7.0",

bằng cách này:

"lodash.sortby": "^4.7.0",

Bây giờ, trong src/index.js, hãy nhập mô-đun cụ thể này:

import "./style.css";
import _ from "lodash";
import sortBy from "lodash.sortby";

Và cập nhật cách sắp xếp các giá trị:

form.addEventListener("submit", e => {
  e.preventDefault();
  const values = [input1.valueAsNumber, input2.valueAsNumber, input3.valueAsNumber];
  const sortedValues = _.sortBy(values);
  const sortedValues = sortBy(values);

  results.innerHTML = `
    <h2>
      ${sortedValues}
    </h2>
  `
});

Tải lại ứng dụng, mở DevTools rồi xem lại bảng điều khiển Network (Mạng).

Bảng điều khiển Mạng hiển thị gói JavaScript 15,2 KB.

Đối với ứng dụng này, kích thước gói đã giảm hơn 4 lần mà không cần nhiều công sức, nhưng vẫn còn nhiều khía cạnh để cải thiện.

Phân tách mã

webpack là một trong những trình đóng gói mô-đun nguồn mở phổ biến nhất hiện nay. Tóm lại, công cụ này gói tất cả các mô-đun JavaScript (cũng như các thành phần khác) tạo nên một ứng dụng web thành các tệp tĩnh mà trình duyệt có thể đọc.

Gói duy nhất được sử dụng trong ứng dụng này có thể được chia thành hai phân đoạn riêng biệt:

  • Một phần chịu trách nhiệm về mã tạo nên tuyến ban đầu của chúng ta
  • Một phần phụ chứa mã sắp xếp

Khi sử dụng tính năng nhập động, bạn có thể tải lười hoặc tải theo yêu cầu một phần phụ. Trong ứng dụng này, mã tạo nên phân đoạn chỉ có thể được tải khi người dùng nhấn nút.

Bắt đầu bằng cách xoá lệnh nhập cấp cao nhất cho phương thức sắp xếp trong src/index.js:

import sortBy from "lodash.sortby";

Sau đó, nhập lớp này vào trình nghe sự kiện sẽ kích hoạt khi nhấn nút:

form.addEventListener("submit", e => {
  e.preventDefault();
  import('lodash.sortby')
    .then(module => module.default)
    .then(sortInput())
    .catch(err => { alert(err) });
});

Tính năng import() là một phần của đề xuất (hiện ở giai đoạn 3 của quy trình TC39) để bao gồm khả năng nhập linh động một mô-đun. Webpack đã hỗ trợ tính năng này và tuân theo cú pháp tương tự do đề xuất đưa ra.

import() trả về một lời hứa và khi lời hứa đó được phân giải, mô-đun đã chọn sẽ được cung cấp và được tách thành một phần riêng biệt. Sau khi mô-đun được trả về, module.default được dùng để tham chiếu hoạt động xuất mặc định do lodash cung cấp. Lời hứa được nối với một .then khác gọi phương thức sortInput để sắp xếp ba giá trị đầu vào. Ở cuối chuỗi lời hứa, .catch() được dùng để xử lý các trường hợp lời hứa bị từ chối do lỗi.

Việc cần làm cuối cùng là viết phương thức sortInput ở cuối tệp. Đây phải là một hàm trả về một hàm lấy phương thức đã nhập từ lodash.sortBy. Sau đó, hàm lồng nhau có thể sắp xếp ba giá trị đầu vào và cập nhật DOM.

const sortInput = () => {
  return (sortBy) => {
    const values = [
      input1.valueAsNumber,
      input2.valueAsNumber,
      input3.valueAsNumber
    ];
    const sortedValues = sortBy(values);

    results.innerHTML = `
      <h2>
        ${sortedValues}
      </h2>
    `
  };
}

Giám Sát

Tải lại ứng dụng một lần nữa và theo dõi chặt chẽ bảng điều khiển Network (Mạng). Chỉ một gói ban đầu nhỏ được tải xuống ngay khi ứng dụng tải.

Bảng điều khiển Mạng hiển thị gói JavaScript 2,7 KB.

Sau khi nhấn nút để sắp xếp các số nhập, phần chứa mã sắp xếp sẽ được tìm nạp và thực thi.

Bảng điều khiển mạng hiển thị gói JavaScript 2,7 KB, theo sau là gói JavaScript 13,9 KB.

Hãy lưu ý cách các số vẫn được sắp xếp!

Kết luận

Tính năng phân tách mã và tải lười có thể là những kỹ thuật cực kỳ hữu ích để cắt giảm kích thước gói ban đầu của ứng dụng. Điều này có thể trực tiếp dẫn đến thời gian tải trang nhanh hơn nhiều. Tuy nhiên, bạn cần cân nhắc một số điều quan trọng trước khi đưa tính năng tối ưu hoá này vào ứng dụng.

Giao diện người dùng tải từng phần

Khi tải lười các mô-đun mã cụ thể, điều quan trọng là bạn phải xem xét trải nghiệm của người dùng có kết nối mạng yếu hơn. Việc phân tách và tải một đoạn mã rất lớn khi người dùng gửi một hành động có thể khiến ứng dụng có vẻ như đã ngừng hoạt động. Vì vậy, hãy cân nhắc hiển thị một chỉ báo tải.

Tải từng phần các mô-đun nút của bên thứ ba

Không phải lúc nào đây cũng là phương pháp tốt nhất để tải từng phần các phần phụ thuộc của bên thứ ba trong ứng dụng của bạn, mà còn phụ thuộc vào vị trí bạn sử dụng các phần phụ thuộc đó. Thông thường, các phần phụ thuộc của bên thứ ba được chia thành một gói vendor riêng biệt có thể được lưu vào bộ nhớ đệm vì các phần phụ thuộc này không cập nhật thường xuyên. Hãy đọc thêm về cách SplitChunksPlugin có thể giúp bạn thực hiện việc này.

Tải từng phần bằng khung JavaScript

Nhiều khung và thư viện phổ biến sử dụng webpack cung cấp các tính năng trừu tượng để tải lười dễ dàng hơn so với việc sử dụng tính năng nhập động ở giữa ứng dụng.

Mặc dù việc hiểu cách hoạt động của tính năng nhập động rất hữu ích, nhưng bạn phải luôn sử dụng phương thức mà khung/thư viện đề xuất để tải từng phần các mô-đun cụ thể.

Tải trước và tìm nạp trước

Nếu có thể, hãy tận dụng các gợi ý của trình duyệt như <link rel="preload"> hoặc <link rel="prefetch"> để thử tải các mô-đun quan trọng sớm hơn. webpack hỗ trợ cả hai gợi ý này thông qua việc sử dụng các nhận xét ma thuật trong câu lệnh nhập. Điều này được giải thích chi tiết hơn trong hướng dẫn Tải trước các phần quan trọng.

Tải từng phần không chỉ áp dụng cho mã

Hình ảnh có thể tạo nên một phần quan trọng của ứng dụng. Tính năng tải từng phần những nội dung nằm bên dưới màn hình đầu tiên hoặc bên ngoài khung nhìn của thiết bị có thể giúp tăng tốc độ của trang web. Đọc thêm về chủ đề này trong hướng dẫn về Lazysizes.