在此 Codelab 中,您应移除所有未使用的和不需要的依赖项,以提高以下应用的性能。
测量
最好先衡量网站的表现,然后再添加优化措施。
- 如需预览网站,请按查看应用,然后按全屏 。
快来点击您喜爱的小猫吧!此应用中使用了 Firebase 的 Realtime Database,因此得分会实时更新,并与使用该应用的其他所有用户同步。🐈
- 按 `Control+Shift+J`(在 Mac 上,则按 `Command+Option+J`)打开开发者工具。
- 点击网络标签页。
- 选中停用缓存复选框。
- 重新加载应用。
为加载这个简单的应用,总共使用了大约 1 MB 的 JavaScript!
查看开发者工具中的项目警告。
- 点击控制台标签页。
- 确保已在
Filter
输入源旁边的级别下拉菜单中启用Warnings
。
- 查看显示的警告。
Firebase 是此应用中使用的库之一,它表现出色,它向开发者发出警告,让开发者知道不要导入整个软件包,而只导入所使用的组件。换句话说,在此应用中有一些未使用的库可以移除,以提高加载速度。
也存在使用特定库的情况,但可能有更简单的替代方案。本教程后面部分探讨了移除不需要的库的概念。
分析 bundle
应用中有两个主要依赖项:
- Firebase:一个为 iOS、Android 或 Web 应用提供许多实用服务的平台。利用它的实时数据库,你可以实时存储并同步每只小猫的信息。
- 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 版本 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';
现在,当应用重新加载时,系统不会显示开发者工具警告。打开开发者工具的 Network 面板还可以显示软件包大小大幅缩减:
有超过一半的套装大小已移除。Firebase 提供了许多不同的服务,让开发者能够选择仅包含实际需要的服务。在此应用中,只有 firebase/database
用于存储和同步所有数据。始终需要 firebase/app
导入,用于为每项不同的服务设置 API Surface。
许多其他热门库(例如 lodash
)也允许开发者有选择地导入其软件包的不同部分。在不执行太多工作的情况下,更新应用中的库导入以仅包含正在使用的内容可以显著提升性能。
虽然软件包大小已缩减了很多,但仍有更多工作要做!😈
移除不需要的软件包
与 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> `}) });
现在,重新加载应用,然后再次查看 Network 面板。
我们的套装大小再次减少了一半以上!
总结
通过此 Codelab,您应该可以充分了解如何分析特定的软件包,以及为什么移除不使用或不需要的软件包会如此有用。在开始使用此方法优化应用之前,请务必注意,对于大型应用,此过程可能要复杂得多。
关于移除未使用的库,请尝试找出软件包的哪些部分正在使用,哪些部分未被使用。如果看起来神秘的软件包似乎未在任何位置使用,请退一步检查哪些顶级依赖项可能需要它。尽量找到一种可能使它们彼此分离的方法。
在移除不需要的库方面,情况可能会稍微复杂一些。请务必与团队密切合作,看看是否有可能简化代码库的某些部分。在此应用中移除 moment
看起来似乎每次都是正确的做法,但如果有时区和不同的语言区域需要处理,该怎么办?或者,如果有更复杂的日期操作,该怎么办?在操纵和解析日期/时间时,情况会变得非常棘手,而 moment
和 date-fns
等库可以显著简化这一过程。
一切都需要权衡利弊,因此务必要衡量发布自定义解决方案(而不是依赖第三方库)的复杂性和工作价值是否值得。