移除未使用的代码

在此 Codelab 中,通过移除所有未使用的和不需要的依赖项,提升以下应用的性能。

应用屏幕截图

测量

在进行优化之前,最好先衡量网站的表现。

  • 如需预览网站,请按 View App(查看应用)。然后按 Fullscreen(全屏)全屏

请点击您喜欢的小猫咪!此应用使用了 Firebase 的 Realtime Database,因此得分会实时更新,并与使用该应用的所有其他用户同步。🐈?

  1. 按 `Control+Shift+J`(在 Mac 上为 `Command+Option+J`)打开 DevTools。
  2. 点击网络标签页。
  3. 选中 Disable cache(停用缓存)复选框。
  4. 重新加载应用。

原始软件包大小为 992 KB

为了加载这个简单的应用,系统要传送近 1 MB 的 JavaScript 代码!

查看 DevTools 中的项目警告。

  • 点击控制台标签页。
  • 确保在 Filter 输入框旁边的“级别”下拉菜单中启用了 Warnings

警告过滤器

  • 查看显示的警告。

控制台警告

Firebase 是此应用中使用的库之一,它会提供警告,告知开发者不要导入其整个软件包,而只导入所使用的组件。换句话说,此应用中有一些未使用的库可以移除,以加快其加载速度。

在某些情况下,使用特定库,但可能有更简单的替代方案。本教程稍后将介绍移除不需要的库的概念。

分析 bundle

该应用有两个主要依赖项:

  • Firebase:一个平台,可为 iOS、Android 或 Web 应用提供多项实用服务。在这里,其 Realtime Database 用于实时存储和同步每只小猫的信息。
  • Moment.js:一个实用程序库,可让您更轻松地在 JavaScript 中处理日期。每只小猫的出生日期都存储在 Firebase 数据库中,moment 用于计算其年龄(以周为单位)。

仅仅两个依赖项如何会导致 bundle 大小接近 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 软件包分析器

虽然没有看到小猫咪那么可爱 🐱?,但还是非常有帮助。 将鼠标悬停在任何软件包上,系统都会以三种不同的方式显示其大小:

统计信息大小 在进行任何缩减或压缩之前的大小。
解析后的大小 软件包在编译后的实际大小。 webpack 版本 4(此应用中使用)会自动缩减已编译的文件,因此其大小小于统计大小。
压缩后的大小 使用 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 大小显著缩减:

软件包大小缩减为 480 KB

移除了超过一半的软件包大小。Firebase 提供许多不同的服务,可让开发者选择仅添加实际需要的服务。在此应用中,仅使用 firebase/database 存储和同步所有数据。firebase/app 导入始终是必需的,它会为每项不同的服务设置 API Surface。

许多其他热门库(例如 lodash)也允许开发者选择性地导入软件包的不同部分。只需更新应用中的库导入内容,使其仅包含正在使用的库,即可在不增加太多工作量的情况下显著提升性能。

虽然 bundle 大小已大幅缩减,但我们仍有许多工作要做!😈

移除不需要的软件包

与 Firebase 不同,导入 moment 库的部分内容并不容易,但或许可以将其完全移除?

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

以 Unix 格式存储的出生日期

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

一如既往,请不要在跟随本教程操作时复制和粘贴内容。首先,从 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 面板。

软件包大小缩减为 225 KB

我们的软件包大小又缩减了一半以上!

总结

通过此 Codelab,您应该已经对如何分析特定软件包有了充分的了解,并了解了移除未使用的或不需要的软件包为何如此有用。在开始使用此技术优化应用之前,请务必注意,在大型应用中,此过程可能会变得更加复杂

关于移除未使用的库,请尝试找出软件包的哪些部分正在使用,哪些部分未使用。对于看起来似乎未在任何位置使用的神秘软件包,请退一步,检查哪些顶级依赖项可能需要它。尝试找到一种可能将它们彼此分离的方法。

至于移除不需要的库,情况可能会稍微复杂一些。请务必与团队密切合作,看看是否有可能简化代码库的某些部分。在这种情况下,移除 moment 似乎是每次都正确的做法,但如果需要处理时区和不同的语言区域,该怎么办?或者,如果日期处理更复杂,该怎么办?在处理和解析日期/时间时,情况可能会非常棘手,而 momentdate-fns 等库可以显著简化此过程。

一切都是权衡的结果,因此请务必评估是否值得投入复杂性和精力来推出自定义解决方案,而不是依赖第三方库。