Xoá các đoạn 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 bằng cách xoá mọi phần phụ thuộc không cần thiết và không dùng đến.

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

Đo

Bạn nên đo lường hiệu suất của một trang web trước khi thêm 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 Fullscreen toàn màn hình (Toàn màn hình).

Hãy tiếp tục và 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 sử dụng trong ứng dụng này. Đó là lý do điểm số sẽ cập nhật theo thời gian thực và được đồng bộ hoá với mọi người khác đang 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 kiểm 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 trị giá của JavaScript đang được chuyển để tải ứng dụng đơn giản này!

Xem cảnh báo dự án trong Công cụ cho nhà phát triển.

  • Hãy nhấp vào thẻ Bảng điều khiển.
  • Hãy nhớ bật Warnings trong trình đơn thả xuống cấp bên cạnh đầu vào Filter.

Bộ lọc cảnh báo

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

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

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

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

Phân tích gói

Có 2 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 iOS, Android hoặc ứng dụng web. Ở đây, Cơ sở dữ liệu theo thời gian thực được dùng để lưu trữ và đồng bộ hoá thông tin cho từng mèo con theo thời gian thực.
  • Moment.js: một thư viện tiện ích giúp việc xử lý các ngày trong JavaScript trở nên 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 mỗi chú mèo con theo tuần.

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

Phân tích trình tạo gói để hiểu rõ hơn về những gì đang diễn ra. Có nhiều công cụ do cộng đồng xây dựng có thể giúp bạn làm 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 cấu hình này trong tệp cấu hình gói web. Nhập mã 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 của toàn bộ gói thay vì chính ứng dụng.

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

Không đáng yêu như nhìn thấy vài chú mèo con Tỷ Có nhu cầu, nhưng dù sao cũng 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 đó theo 3 cách:

Kích thước số liệu thống kê Kích thước trước khi thu nhỏ 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 gói đó được biên dịch. Phiên bản 4 của gói web (dùng trong ứng dụng này) sẽ tự động giảm kích thước các tệp được biên dịch. Đó là lý do tại sao kích thước này nhỏ hơn kích thước của số liệu thống kê.
Kích thước nén Kích thước của gói sau khi được nén bằng mã hóa gzip. Chủ đề này có trong một hướng dẫn riêng.

Nhờ 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 sử dụng

Hình ảnh trực quan cho thấy gói firebase bao gồm nhiều hơn một cơ sở dữ liệu. Trong đó có các gói bổ sung như:

  • firestore
  • auth
  • storage
  • messaging
  • functions

Đây đều là tất cả các dịch vụ tuyệt vời do Firebase cung cấp (và 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ố đó đang được sử dụng trong ứng dụng, vì vậy không có lý do gì để nhập tất cả chúng.

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á dữ liệu nhập không dùng đến khỏi đầu tệp:
const path = require("path");

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

Lúc này ứng dụng sẽ tải bình thường. Sửa đổi src/index.js để cập nhật dữ liệu nhập từ Firebase.

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

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

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 lựa chọn chỉ đưa vào những dịch vụ thực sự cần thiết. Trong ứng dụng này, chỉ firebase/database được dùng để lưu trữ và đồng bộ hoá tất cả dữ liệu. Bạn luôn bắt buộc 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ác phần khác nhau trong gói một cách có chọn lọc. Bạn không cần làm gì nhiều, việc cập nhật các mục nhập thư viện trong một ứng dụng để chỉ bao gồm những nội dung đang được sử dụng có thể mang đến những cải thiện đáng kể về hiệu suất.

Mặc dù kích thước gói đã giảm đáng kể, nhưng bạn vẫn cần làm nhiều việc hơn! 😈

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

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

Ngày sinh của mỗi chú mèo con đáng yêu đượ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ừ 00:00 ngày 1 tháng 1 năm 1970 (giờ UTC). Nếu có thể tính toán ngày và giờ hiện tại ở cùng định dạng, thì bạn có thể tạo một hàm nhỏ để tìm tuổi của từng chú mèo con tính theo tuần.

Như mọi khi, đừng sao chép và dán khi bạn làm theo các bước này. Bắt đầu bằng cách xoá moment khỏi các mục 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:

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

Ở phía trên, 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, mức 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ả 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).

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

Kích thước gói của chúng tôi lại giảm được hơn một nửa!

Kết luận

Với lớp học lập trình này, bạn đã nắm rõ cách phân tích một gói cụ thể cũng như 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. Trước khi bắt đầu tối ưu hoá một ứng dụng bằng kỹ thuật này, bạn cần biết rằng quy trình này có thể phức tạp hơn đáng kể trong các ứng dụng lớn hơn.

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

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

Mọi thứ đều phải đánh đổi và điều quan trọng là 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à cần nỗ lực hay không thay vì dựa vào thư viện của bên thứ ba hay không.