移除未使用的代码

在此代码实验室中,通过移除任何未使用的和不需要的依赖项来提高以下应用程序的性能。

App screenshot

测量

在添加优化之前,最好先测量网站的性能。

  • 要预览网站,请按查看应用。然后按全屏 全屏

继续并点击您最喜欢的猫咪!Firebase 的 Realtime Database 在此应用程序中使用,这就是为什么分数实时更新并与使用该应用程序的每个人同步的原因。 🐈

  1. 按 `Control+Shift+J`(或 Mac 上的 `Command+Option+J`)打开 DevTools。
  2. 点击 Network 选项卡。
  3. 选中 Disable cache 复选框。
  4. 重新加载应用程序。

Original bundle size of 992 KB

加载这个简单的应用程序竟然运送了将近 1 MB 的 JavaScript!

查看 DevTools 中的项目警告。

  • 点击 Console 选项卡。
  • 确保在 Filter 输入框旁边的级别下拉列表中启用了 Warnings

Warnings filter

  • 查看显示的警告。

Console warning

Firebase 是此应用程序中使用的库之一,它充当了一个好心人,提供了一个警告,让开发者知道不要导入它的整个软件包,而只导入使用的组件。换句话说,此应用程序中可以移除未使用的库,以加快加载速度。

在某些情况下,也使用了特定的库,但可能存在更简单的替代方案。删除不需要的库的概念将在本教程的后面部分探讨。

分析捆绑包

应用程序中有两个主要依赖项

  • Firebase:一个为 iOS、Android 或 Web 应用程序提供许多有用服务的平台。在这里,它的 Realtime Database 用于实时存储和同步每个猫咪的信息。
  • Moment.js:一个实用程序库,使处理 JavaScript 中的日期更加容易。每个猫咪的出生日期都存储在 Firebase 数据库中,并且 moment 用于计算其以周为单位的年龄。

仅仅两个依赖项怎么会贡献了将近 1 MB 的捆绑包大小?嗯,原因之一是任何依赖项反过来都可以有自己的依赖项,因此如果考虑依赖项“树”的每个深度/分支,则不仅仅是两个。如果包含许多依赖项,应用程序很容易相对快速地变大。

分析捆绑器以更好地了解正在发生的事情。有许多不同的社区构建工具可以帮助做到这一点,例如 webpack-bundle-analyzer

此工具的软件包已作为 devDependency 包含在应用程序中。

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

这意味着它可以直接在 webpack 配置文件中使用。在 webpack.config.js 的开头导入它

const path = require("path");

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

现在将其作为插件添加到文件末尾的 plugins 数组中

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

当应用程序重新加载时,您应该会看到整个捆绑包的可视化效果,而不是应用程序本身。

Webpack Bundle Analyzer

不如看到一些小猫 🐱 那么可爱,但仍然非常有用。将鼠标悬停在任何软件包上都会以三种不同的方式显示其大小

Stat 大小 在任何缩小或压缩之前的大小。
Parsed 大小 编译后捆绑包中实际软件包的大小。此应用程序中使用的 webpack 版本 4 会自动缩小编译后的文件,这就是为什么它比 stat 大小小的原因。
Gzipped 大小 使用 gzip 编码压缩后软件包的大小。本主题将在单独的指南中介绍。

借助 webpack-bundle-analyzer 工具,可以更轻松地识别构成捆绑包很大一部分的未使用或不需要的软件包。

移除未使用的软件包

可视化效果显示 firebase 软件包包含的不止数据库。它包括其他软件包,例如

  • firestore
  • auth
  • storage
  • messaging
  • functions

这些都是 Firebase 提供的非常棒的服务(并参考文档了解更多信息),但这些服务都没有在此应用程序中使用,因此没有理由导入所有这些服务。

恢复 webpack.config.js 中的更改以再次查看应用程序

  • 移除插件列表中的 BundleAnalyzerPlugin
plugins: [
  //...
  new BundleAnalyzerPlugin()
];
  • 现在从文件顶部移除未使用的导入
const path = require("path");

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

应用程序现在应该正常加载。修改 src/index.js 以更新 Firebase 导入。

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

现在,当应用程序重新加载时,DevTools 警告不再显示。打开 DevTools Network 面板也显示捆绑包大小大大减小了

Bundle size reduced to 480 KB

超过一半的捆绑包大小被移除。Firebase 提供了许多不同的服务,并让开发者可以选择仅包含实际需要的服务。在此应用程序中,仅使用了 firebase/database 来存储和同步所有数据。firebase/app 导入是始终需要的,它为每种不同的服务设置了 API 表面。

许多其他流行的库(例如 lodash)也允许开发者有选择地导入其软件包的不同部分。无需做太多工作,更新应用程序中的库导入以仅包含正在使用的内容可以带来显著的性能改进。

虽然捆绑包大小已经减少了很多,但仍有更多工作要做!😈

移除不需要的软件包

与 Firebase 不同,导入 moment 库的部分内容不能轻易完成,但也许可以完全移除它?

每个可爱猫咪的生日都以 Unix 格式(毫秒)存储在 Firebase 数据库中。

Birthdates stored in Unix format

这是一个特定日期和时间的时间戳,以自 1970 年 1 月 1 日 00:00 UTC 以来经过的毫秒数表示。如果可以以相同的格式计算当前日期和时间,则可以构建一个小函数来查找每只猫咪的年龄(以周为单位)。

与往常一样,在此处跟随时,尽量不要复制和粘贴。首先从 src/index.js 的导入中移除 moment

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

有一个 Firebase 事件侦听器处理数据库中的值更改

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

在此之上,添加一个小函数来计算给定日期之后的周数

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);
}

在此函数中,计算当前日期和时间 (new Date).getTime() 与出生日期(birthDate 参数,已以毫秒为单位)之间的毫秒差,并除以一周中的毫秒数。

最后,可以通过利用此函数移除事件侦听器中 moment 的所有实例

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>
    `})
});

现在重新加载应用程序,并再次查看 Network 面板。

Bundle size reduced to 225 KB

我们的捆绑包大小又减少了一半以上!

结论

通过此代码实验室,您应该对如何分析特定捆绑包以及为什么移除未使用或不需要的软件包如此有用有了一定的了解。在开始使用此技术优化应用程序之前,重要的是要知道,在较大的应用程序中,这可能会复杂得多

关于移除未使用的库,请尝试找出捆绑包的哪些部分正在使用,哪些部分未使用。对于看起来像是任何地方都没有使用的神秘软件包,请退后一步,检查哪些顶级依赖项可能需要它。尝试找到一种可能将它们彼此解耦的方法。

当涉及到移除不需要的库时,事情可能会变得稍微复杂一些。重要的是与您的团队紧密合作,看看是否有简化部分代码库的潜力。在此应用程序中移除 moment 可能看起来像是每次都应该做的事情,但是如果需要处理时区和不同的语言环境怎么办?或者如果有更复杂的日期操作怎么办?在操作和解析日期/时间时,事情可能会变得非常棘手,而像 momentdate-fns 这样的库可以显著简化这一点。

一切都是权衡,重要的是衡量推出自定义解决方案而不是依赖第三方库是否值得付出复杂性和努力。