Xoá mã không dùng đến

Trong lớp học lập trình này, hãy cải thiện hiệu suất của ứng dụng sau đây bằng cách xoá mọi phần phụ thuộc không dùng đến và không cần thiết.

Ảnh chụp màn hình ứng dụng

Đo lường

Bạn nên đo lường hiệu suất của trang web trước khi thêm các tính năng tối ưu hoá.

  • Để xem trước trang web, hãy nhấn vào View App (Xem ứng dụng). Sau đó, nhấn vào biểu tượng Fullscreen (Toàn màn hình) toàn màn hình.

Hãy nhấp vào chú mèo con bạn yêu thích! Cơ sở dữ liệu theo thời gian thực của Firebase được dùng trong ứng dụng này, đó là lý do điểm số được cập nhật theo thời gian thực và đồng bộ hoá với mọi người dùng khác đang sử dụng ứng dụng. 🐈

  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 đánh dấu Tắt bộ nhớ đệm.
  4. Tải lại ứng dụng.

Kích thước gói ban đầu là 992 KB

Gần 1 MB JavaScript đang được gửi để tải ứng dụng đơn giản này!

Hãy xem các cảnh báo về dự án trong Công cụ cho nhà phát triển.

  • Nhấp vào thẻ Bảng điều khiển.
  • Đảm bảo bạn đã bật Warnings trong trình đơn thả xuống cấp độ bên cạnh dữ liệu đầu vào Filter.

Bộ lọc cảnh báo

  • Hãy xem cảnh báo được hiển thị.

Cảnh báo trên bảng điều khiển

Firebase (một trong những thư viện được dùng trong ứng dụng này) đang đóng vai trò là một người tốt khi đưa ra cảnh báo để nhà phát triển biết rằng họ không nên nhập toàn bộ gói mà chỉ nên nhập các thành phần được sử dụng. Nói cách khác, có các thư viện không dùng đến có thể bị xoá trong ứng dụng này để giúp ứng dụng tải nhanh hơn.

Cũng có những trường hợp sử dụng một thư viện cụ thể, nhưng có thể có một giải pháp thay thế đơn giản hơn. Khái niệm xoá thư viện không cần thiết sẽ được tìm hiểu kỹ hơn trong phần sau của hướng dẫn này.

Đang phân tích gói

Có hai phần phụ thuộc chính trong ứng dụng:

  • Firebase: một nền tảng cung cấp một số dịch vụ hữu ích cho ứng dụng iOS, Android hoặc ứng dụng web. Tại đây, Cơ sở dữ liệu theo thời gian thực của Firebase được dùng để lưu trữ và đồng bộ hoá thông tin cho từng chú mèo con theo thời gian thực.
  • Moment.js: một thư viện tiện ích giúp xử lý ngày trong JavaScript dễ dàng hơn. Ngày sinh của mỗi chú mèo con được lưu trữ trong cơ sở dữ liệu Firebase và moment được dùng để tính tuổi của chú mèo con theo tuần.

Làm thế nào mà chỉ có hai phần phụ thuộc lại có thể khiến kích thước gói lên đến gần 1 MB? Một trong những lý do là bất kỳ phần phụ thuộc nào cũng có thể có phần phụ thuộc riêng, vì vậy, có nhiều hơn hai phần phụ thuộc nếu xem xét mọi độ sâu/nhánh của "cây" phần phụ thuộc. Ứng dụng có thể dễ dàng trở nên lớn hơn một cách tương đối nhanh chóng nếu có nhiều phần phụ thuộc.

Phân tích trình đóng gói để hiểu rõ hơn về những gì đang diễn ra. Có một số công cụ do cộng đồng xây dựng có thể giúp bạn thực hiện việc này, chẳng hạn như webpack-bundle-analyzer.

Gói cho công cụ này đã được đưa vào ứng dụng dưới dạng devDependency.

"devDependencies": {
  //...
  "webpack-bundle-analyzer": "^2.13.1"
},

Điều này có nghĩa là bạn có thể sử dụng trực tiếp trong tệp cấu hình webpack. Nhập tệp này ngay từ đầu webpack.config.js:

const path = require("path");

//...
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
  .BundleAnalyzerPlugin;

Bây giờ, hãy thêm tệp này dưới dạng một trình bổ trợ ở cuối tệp trong mảng plugins:

module.exports = {
  //...
  plugins: [
    //...
    new BundleAnalyzerPlugin()
  ]
};

Khi ứng dụng tải lại, bạn sẽ thấy hình ảnh trực quan của toàn bộ gói thay vì chính ứng dụng đó.

Trình phân tích gói Webpack

Tuy không dễ thương như mèo con 🐱, nhưng lại cực kỳ hữu ích. Khi di chuột qua bất kỳ gói nào, bạn sẽ thấy kích thước của gói đó được biểu thị theo 3 cách:

Kích thước số liệu thống kê Kích thước trước khi rút gọn hoặc nén.
Kích thước được phân tích cú pháp Kích thước của gói thực tế trong gói sau khi được biên dịch. Phiên bản 4 của webpack (được dùng trong ứng dụng này) tự động giảm thiểu các tệp đã biên dịch, đó là lý do tại sao kích thước này nhỏ hơn kích thước thống kê.
Kích thước nén bằng gzip Kích thước của gói sau khi được nén bằng phương thức mã hoá gzip. Chủ đề này được đề cập trong một hướng dẫn riêng.

Với công cụ webpack-bundle-analyzer, bạn có thể dễ dàng xác định các gói không dùng đến hoặc không cần thiết chiếm tỷ lệ lớn trong gói.

Xoá các gói không dùng đến

Hình ảnh trực quan cho thấy gói firebase bao gồm nhiều thành phần hơn chứ không chỉ có cơ sở dữ liệu. Gói này bao gồm các gói bổ sung như:

  • firestore
  • auth
  • storage
  • messaging
  • functions

Đây đều là những dịch vụ tuyệt vời do Firebase cung cấp (và hãy tham khảo tài liệu để tìm hiểu thêm), nhưng không có dịch vụ nào trong số này được dùng trong ứng dụng, vì vậy, không có lý do gì để nhập tất cả các dịch vụ đó.

Huỷ bỏ các thay đổi trong webpack.config.js để xem lại ứng dụng:

  • Xoá BundleAnalyzerPlugin trong danh sách trình bổ trợ:
plugins: [
  //...
  new BundleAnalyzerPlugin()
];
  • Bây giờ, hãy xoá lệnh nhập không dùng đến ở đầu tệp:
const path = require("path");

//...
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

Giờ đây, ứng dụng sẽ tải bình thường. Sửa đổi src/index.js để cập nhật các mục nhập Firebase.

import firebase from 'firebase';
import firebase from 'firebase/app';
import 'firebase/database';

Bây giờ, khi ứng dụng tải lại, cảnh báo của Công cụ cho nhà phát triển sẽ không xuất hiện. Khi mở bảng điều khiển Network (Mạng) của Công cụ cho nhà phát triển, bạn cũng sẽ thấy kích thước gói giảm đáng kể:

Kích thước gói giảm xuống còn 480 KB

Đã xoá hơn một nửa kích thước gói. Firebase cung cấp nhiều dịch vụ khác nhau và cho phép nhà phát triển chỉ đưa vào những dịch vụ thực sự cần thiết. Trong ứng dụng này, chỉ có firebase/database được dùng để lưu trữ và đồng bộ hoá tất cả dữ liệu. Bạn luôn phải nhập firebase/app để thiết lập nền tảng API cho từng dịch vụ.

Nhiều thư viện phổ biến khác, chẳng hạn như lodash, cũng cho phép nhà phát triển nhập có chọn lọc các phần khác nhau trong gói của họ. Không cần làm gì nhiều, việc cập nhật các lệnh nhập thư viện trong một ứng dụng để chỉ bao gồm những gì đang được sử dụng có thể giúp cải thiện đáng kể hiệu suất.

Mặc dù kích thước gói đã giảm đi khá nhiều, nhưng vẫn còn nhiều việc phải làm! 😈

Xoá các gói không cần thiết

Không giống như Firebase, việc nhập các phần của thư viện moment không dễ dàng như vậy, nhưng có thể xoá hoàn toàn thư viện này?

Ngày sinh của mỗi chú mèo con dễ thương được lưu trữ ở định dạng Unix (mili giây) trong cơ sở dữ liệu Firebase.

Ngày sinh được lưu trữ ở định dạng Unix

Đây là dấu thời gian của một ngày và giờ cụ thể, được biểu thị bằng số mili giây đã trôi qua kể từ ngày 1 tháng 1 năm 1970, lúc 00:00 theo giờ UTC. Nếu có thể tính ngày và giờ hiện tại theo cùng một định dạng, thì có thể tạo một hàm nhỏ để tìm tuổi của mỗi chú mèo con theo tuần.

Như thường lệ, hãy cố gắng không sao chép và dán khi bạn làm theo hướng dẫn tại đây. Bắt đầu bằng cách xoá moment khỏi các lệnh nhập trong src/index.js.

import firebase from 'firebase/app';
import 'firebase/database';
import * as moment from 'moment';

Có một trình nghe sự kiện Firebase xử lý các thay đổi về giá trị trong cơ sở dữ liệu của chúng ta:

favoritesRef.on("value", (snapshot) => { ... })

Phía trên hàm này, hãy thêm một hàm nhỏ để tính số tuần kể từ một ngày nhất định:

const ageInWeeks = birthDate => {
  const WEEK_IN_MILLISECONDS = 1000 * 60 * 60 * 24 * 7;
  const diff = Math.abs((new Date).getTime() - birthDate);
  return Math.floor(diff / WEEK_IN_MILLISECONDS);
}

Trong hàm này, chênh lệch tính bằng mili giây giữa ngày và giờ hiện tại (new Date).getTime() và ngày sinh (đối số birthDate, đã tính bằng mili giây) được tính và chia cho số mili giây trong một tuần.

Cuối cùng, bạn có thể xoá tất cả các thực thể của moment trong trình nghe sự kiện bằng cách tận dụng hàm này:

favoritesRef.on("value", (snapshot) => {
  const { kitties, favorites, names, birthDates } = snapshot.val();
  favoritesScores = favorites;

  kittiesList.innerHTML = kitties.map((kittiePic, index) => {
    const birthday = moment(birthDates[index]);

    return `
      <li>
        <img src=${kittiePic} onclick="favKittie(${index})">
        <div class="extra">
          <div class="details">
            <p class="name">${names[index]}</p>
            <p class="age">${moment().diff(birthday, 'weeks')} weeks old</p>
            <p class="age">${ageInWeeks(birthDates[index])} weeks old</p>
          </div>
          <p class="score">${favorites[index]} ❤</p>
        </div>
      </li>
    `})
});

Bây giờ, hãy tải lại ứng dụng và xem lại bảng điều khiển Network (Mạng) một lần nữa.

Kích thước gói giảm xuống còn 225 KB

Kích thước của gói đã giảm hơn một nửa!

Kết luận

Qua lớp học lập trình này, bạn sẽ hiểu rõ cách phân tích một gói cụ thể và lý do việc xoá các gói không dùng đến hoặc không cần thiết lại hữu ích đến vậy. Trước khi bắt đầu tối ưu hoá ứng dụng bằng kỹ thuật này, bạn cần biết rằng việc này có thể phức tạp hơn đáng kể trong các ứng dụng lớn hơn.

Đối với việc xoá các thư viện không dùng đến, hãy cố gắng tìm hiểu xem phần nào của gói đang được sử dụng và phần nào không. Đối với một gói có vẻ bí ẩn và dường như không được sử dụng ở bất cứ đâu, hãy lùi lại một bước và kiểm tra xem phần phụ thuộc cấp cao nhất nào có thể cần đến gói đó. Hãy cố gắng tìm cách tách chúng ra khỏi nhau.

Khi xoá các thư viện không cần thiết, mọi thứ có thể phức tạp hơn một chút. Bạn cần phối hợp chặt chẽ với nhóm của mình và xem liệu có thể đơn giản hoá các phần của cơ sở mã hay không. Việc xoá moment trong ứng dụng này có vẻ như là việc nên làm, nhưng nếu có múi giờ và ngôn ngữ khác cần xử lý thì sao? Hoặc nếu có các thao tác phức tạp hơn về ngày thì sao? Mọi thứ có thể trở nên rất phức tạp khi thao tác và phân tích cú pháp ngày/giờ, và các thư viện như moment và date-fns giúp đơn giản hoá việc này đáng kể.

Mọi thứ đều có sự đánh đổi và điều quan trọng là bạn phải đánh giá xem liệu có đáng để triển khai một giải pháp tuỳ chỉnh phức tạp và tốn công sức thay vì dựa vào thư viện của bên thứ ba hay không.