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 bao gồm nhiều phần. 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 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 sai lầm phổ biến là cài đặt và nhập toàn bộ phần phụ thuộc của bên thứ ba trong đó chỉ một phần nhỏ được sử dụng.

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, chúng tôi không sử dụng các phương thức đó trong hướng dẫn này vì 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, 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:

"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 tốn nhiều công sức, nhưng vẫn còn nhiều điểm cần 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 đoạn mã 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 đang ở giai đoạn 3 của quy trình TC39) để bao gồm khả năng nhập mô-đun một cách linh động. webpack đã hỗ trợ tính năng này và tuân theo cú pháp giống như đề xuất.

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 cuối cùng cần làm là viết phương thức sortInput ở cuối tệp. Đây cần 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 để 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

Đây không phải lúc nào 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 và điều này 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 nhiều hơn mã

Hình ảnh có thể là 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.