移除未使用的代码

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

应用屏幕截图

测量

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

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

请点击您喜欢的小猫咪!此应用使用 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 用于计算其年龄(以周为单位)。

为什么只有两个依赖项会造成将近 1 MB 的 app bundle 大小?原因之一是,任何依赖项都可能有自己的依赖项,因此如果考虑依赖项“树”的每个深度/分支,就不仅仅是两个依赖项。如果包含许多依赖项,应用很容易相对较快地变大。

分析捆绑程序,以便更好地了解发生的情况。许多社区构建的工具(例如 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 压缩大小 使用 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 面板后,还会发现软件包大小显著缩减:

捆绑包大小缩减到 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 等库可以显著简化这一点。

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