在此 Codelab 中,您将通过移除所有未使用的不必要依赖项来提升以下应用的性能。
测量
在添加优化之前,最好先衡量网站的性能。
- 如需预览网站,请按查看应用。然后按全屏图标
。
继续操作,点击您最喜欢的小猫!此应用中使用了 Firebase 的实时数据库,因此得分会实时更新,并与使用该应用的其他人同步。🐈
- 按 `Control+Shift+J`(在 Mac 上,按 `Command+Option+J`)打开开发者工具。
- 点击网络标签页。
- 选中停用缓存复选框。
- 重新加载应用。
为了加载这个简单的应用,我们发送了近 1 MB 的 JavaScript!
在开发者工具中查看项目警告。
- 点击控制台标签页。
- 确保在
Filter
输入旁边的“级别”下拉菜单中启用了Warnings
。
- 查看显示的警告。
Firebase 是此应用中使用的一个库,它通过提供警告来提醒开发者不要导入整个软件包,而只导入所使用的组件,从而充当了“好撒玛利亚人”。换句话说,此应用中存在未使用的库,可以将其移除以加快加载速度。
此外,在某些情况下,虽然使用了某个特定库,但可能存在更简单的替代方案。本教程稍后将探讨移除不需要的库的概念。
分析 bundle
应用中有两个主要依赖项:
- 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()
]
};
当应用重新加载时,您应该会看到整个软件包的可视化效果,而不是应用本身。
虽然不如看到一些小猫 🐱 那么可爱,但仍然非常有用。 将鼠标悬停在任何软件包上,系统都会以三种不同的方式显示其大小:
统计数据大小 | 未经过任何缩小或压缩处理的大小。 |
---|---|
解析后的大小 | 软件包在编译后,在 bundle 中的实际大小。 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 语句。
import firebase from 'firebase';
import firebase from 'firebase/app';
import 'firebase/database';
现在,当应用重新加载时,开发者工具警告不会显示。打开开发者工具的网络面板后,您还会发现软件包大小明显减小了:
移除了超过一半的软件包大小。Firebase 提供许多不同的服务,并允许开发者仅包含实际需要的服务。在此应用中,仅使用 firebase/database
来存储和同步所有数据。firebase/app
导入(用于为每项不同的服务设置 API 途径)始终是必需的。
许多其他热门库(例如 lodash
)也允许开发者有选择地导入其软件包的不同部分。无需执行太多工作,只需更新应用中的库导入,使其仅包含正在使用的内容,即可显著提升性能。
虽然软件包大小已大幅减小,但仍有更多工作要做!😈
移除不需要的软件包
与 Firebase 不同,导入 moment
库的部分内容并不容易,但或许可以完全移除?
每只可爱小猫的生日都以 Unix 格式(以毫秒为单位)存储在 Firebase 数据库中。
这是特定日期和时间的时间戳,以自 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 大小再次缩减了一半以上!
总结
完成此 Codelab 后,您应该能够很好地了解如何分析特定软件包,以及移除未使用的或不需要的软件包为何如此有用。在开始使用此技术优化应用之前,请务必了解,在大型应用中,此技术可能会复杂得多。
关于移除未使用的库,请尝试找出软件包中哪些部分正在使用,哪些部分未使用。对于看起来在任何地方都没有使用的神秘软件包,请退一步,检查哪些顶级依赖项可能需要它。尝试找到一种方法,将它们彼此解耦。
在移除不必要的库方面,情况可能会稍微复杂一些。请务必与团队密切合作,看看是否有简化部分代码库的潜力。在此应用中移除 moment
似乎每次都是正确的做法,但如果需要处理时区和不同的语言区域,该怎么办?或者,如果日期操作更复杂,会怎么样?在处理和解析日期/时间时,情况可能会非常复杂,而 moment
和 date-fns
等库可显著简化此过程。
任何事物都是一种权衡,因此,在考虑是推出自定义解决方案还是依赖第三方库时,务必要评估前者是否值得付出相应的复杂性和努力。