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

测量
在添加优化之前,最好先衡量网站的效果。
- 如需预览网站,请按查看应用。然后按全屏
。
快来点击您最喜欢的小猫吧!此应用中使用了 Firebase 的 Realtime Database,因此分数会实时更新,并与使用该应用的每个其他用户同步。🐈
- 按 `Control+Shift+J`(在 Mac 上,按 `Command+Option+J`)打开开发者工具。
- 点击网络标签页。
- 选中停用缓存复选框。
- 重新加载应用。

为了加载这个简单的应用,需要传输近 1 MB 的 JavaScript!
查看开发者工具中的项目警告。
- 点击控制台标签页。
- 确保在
Filter输入旁边的级别下拉菜单中启用了Warnings。

- 查看显示的警告。

Firebase 是此应用中使用的库之一,它提供警告来让开发者知道不要导入其整个软件包,而只导入使用的组件,从而提供帮助。换句话说,此应用中存在可以移除的未使用库,移除后可加快加载速度。
在某些情况下,虽然使用了特定库,但可能有更简单的替代方案。本教程稍后将探讨移除不必要的库的概念。
正在分析软件包
应用中有两个主要依赖项:
- 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()
]
};
应用重新加载后,您应该会看到整个 bundle 的可视化图表,而不是应用本身。

虽然没有看到小猫咪 🐱 那么可爱,但绝对非常实用。 将鼠标悬停在任何软件包上,系统会以三种不同的方式显示其大小:
| 统计数据大小 | 缩减大小或压缩之前的大小。 |
|---|---|
| 解析后的大小 | 编译后,软件包中实际安装包的大小。 webpack 的第 4 版(此应用中使用的版本)会自动缩减已编译文件的大小,这就是为什么此大小小于统计大小。 |
| Gzip 压缩后的大小 | 使用 gzip 编码压缩后的软件包大小。此主题在单独的指南中介绍。 |
借助 webpack-bundle-analyzer 工具,您可以更轻松地识别在软件包中占很大比例的未使用或不需要的软件包。
移除未使用的软件包
可视化图表显示,firebase 软件包不仅仅包含一个数据库,还包含许多其他内容。它包含其他软件包,例如:
firestoreauthstoragemessagingfunctions
这些都是 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';
现在,当应用重新加载时,开发者工具警告不会显示。打开开发者工具网络面板,也会显示软件包大小大幅缩减:

已移除超过一半的软件包大小。Firebase 提供许多不同的服务,并允许开发者选择仅包含实际需要的服务。在此应用中,仅使用 firebase/database 来存储和同步所有数据。firebase/app 导入始终是必需的,它会为每项不同的服务设置 API Surface。
许多其他热门库(例如 lodash)也允许开发者选择性地导入其软件包的不同部分。无需执行太多工作,只需更新应用中的库导入,使其仅包含正在使用的内容,即可显著提升性能。
虽然 bundle 大小已大幅缩减,但仍有更多工作要做!😈
移除不需要的软件包
与 Firebase 不同,导入 moment 库的部分内容并不容易,但也许可以将其完全移除?
每只可爱小猫的生日都以 Unix 格式(毫秒)存储在 Firebase 数据库中。

这是特定日期和时间的时间戳,以自世界协调时间 (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>
`})
});
现在,重新加载应用,并再次查看网络面板。

我们的 bundle 大小再次缩减了一半以上!
总结
通过本 Codelab,您应该已经充分了解如何分析特定 bundle,以及为什么移除未使用或不需要的软件包会如此有用。在开始使用此技术优化应用之前,请务必了解,在较大的应用中,这可能会复杂得多。
关于移除未使用的库,请尝试找出 bundle 的哪些部分正在使用,哪些部分未使用。对于看起来似乎未在任何地方使用的神秘软件包,请退后一步,检查哪些顶级依赖项可能需要它。尝试找到一种方法, 尽可能将它们彼此解耦。
在移除不需要的库方面,情况可能会稍微复杂一些。请务必与团队密切合作,看看是否有机会简化部分代码库。在此应用中移除 moment 似乎每次都是正确的做法,但如果需要处理时区和不同的语言区域,该怎么办?或者,如果日期操作更复杂,该怎么办?在操作和解析日期/时间时,事情可能会变得非常棘手,而 moment 和 date-fns 等库可以显著简化此过程。
凡事都有利弊,因此请务必衡量是否值得花费精力去推出自定义解决方案,而不是依赖第三方库。