开发人员关注 JavaScript 包的大小,我们也在关注这个问题,因为我们不希望 Sentry 包成为拖慢系统的原因之一。因为新功能往往意味着更大的包,所以为有效地优化 JavaScript SDK 包的大小,我们必须在控制程序包大小的同时兼顾到新功能的发布,比如能够管理版本健康状况和监控性能。
在通过重构来缩小程序包的同时支持之后的新功能开发,这不是我们要面临的唯一挑战,我们要做的重构还有可能会给那些自己写集成的人带来破坏性修改。在运输第三方 JavaScript 库来跟踪错误和延迟问题时,我们务必要谨慎确认之后再引入破坏性修改,尤其是运输那种帮用户找到并解决错误和延迟问题的库。
在发布了 v6 更新后,我们创建并发布了一个路线图,确保我们能够在不改变删除Sentry SDK 的公共 API(如Sentry.captureException和 Sentry.captureMessage)的条件下发布一个新的主版本。这个主要更新还包括扩大 SDK 的摇树(删除死代码)功能,用户可以使用此功能删除自己不需要的代码,进一步缩小程序包。
定义成功的标准及测试
刚开始,我们定下了将 CDN 包缩小 30% 的目标。这个目标是通过对快赢(约 15%)和更广泛的重构(约 15%)的分析后估算出来的。
搞清楚用什么来衡量和记录程序包的大小是一项挑战,因为这取决于应用程序的类型和对 Sentry SDK 的使用。为确保测量的客观性和一致性,我们选择使用 Andrey Sitnik 的 size-limit 库来跟踪 minified CDN bundle 的大小。使用 size-limit 意味着我们可以计算每个 PR 上的包的大小,让开发人员看到对包的大小的修改产生的影响。
我们选择跟踪已被最小化的 CDN 包,而不是压缩+最小化的 CDN 包,因为最小化的包更能代表运行时被执行的包。运行时的数据包大小与解析和执行时间有直接关系,所以最小化的数据包能使 Sentry 阻塞主线程的时间最小化,记录个别修改对最小化包的大小的影响比追踪总的压缩包+最小化包的大小要容易的多。
为了跟踪摇树的进展,我们创建了一个场景列表,并在一段时间内检查 webpack 可视化打包分析的输出,以监测包括哪些模块。这有助于我们验证某些修改是否优化了 SDK的摇树功能。
路线图的范围
Sentry JavaScript v7 路线图有一系列的步骤,需要紧接着主版本之前和之后发生。这样分割非常重要,因为我们需要为 SDK 开发人员打下基础,让他们更容易做出破坏性修改,而且从主要开发分支拉出的改变可以最大程度地减少主要发布分支的开发时间。
主要有几个目标:
- 已建资产默认从 es5 切换到 es6
- 删除废弃的代码(更少代码,更少字节!)
- 去除不必要的抽象(更少代码,更少字节!)
- 对传输、集成和堆栈跟踪分析器启用摇树功能
- 添加摇树标志,便于用户删除生产应用中不需要的 Sentry 逻辑
几句题外话:
- 将我们默认生成的 JavaScript 转换为 target ES6 而不是 ES5。这意味着我们只支持开箱即用的 ES6 兼容浏览器。用户可以使用 Babel 等编译器将我们的 ES6 代码向下编译为 ES5 或更低版本,以支持旧的浏览器/节点版本。ES6 产生的代码比 ES5 更小,所以它将自动为用户节省代码包的大小。
- 我们对废弃代码的删除是相当直接的,而且生成了合适的数据包尺寸。
移除不必要的抽象
我们意识到,抽象虽然能使代码更简洁,却造成了不必要的字节。例如,我们有一个后端类,用于在普通的 Sentry JavaScript 客户端上配置平台特定(节点与浏览器)功能。
这里的问题是,我们也有平台特定的客户端类,它们是一个普通的 BaseClient 类的子类。尽管将这些逻辑提取到一个单独的类中很有用,可以更清晰地分离关注点,但将所有的逻辑放在平台中可以让特定的客户端节省大量的字节。
启用 Sentry 功能的摇树
我们从 Sentry 用户收到的反馈是希望能删除不需要的代码,例如,删除特定的集成。这在 Sentry JavaScript SDK 最初的结构中是不可能的,因为我们把合理的默认值作为 Sentry 客户端类的一部分,在用户调用 Sentry.init 时创建。这意味着,即使用户过滤掉了一个默认的集成,这个集成仍然会被包括在内,因为它被内部引用到了 Sentry 客户端类中。
为改变这种情况,我们把通常的用户摇树的逻辑从 Sentry 客户端类的内部状态中提取出来,注入到客户端类的依赖数据中。
为说明这一点,我们看一下下列 Sentry.init 函数的例子,以及它之前的操作。
// Sentry.init() call
function init(options) {
const client = new Client(options);
startClient(client);
}
class Client {
constructor(options) {
this.options = {
// functions chooses correct values for client
// based on SDK set defaults and options
transport: this.getTransport(options),
integrations: this.getIntegrations(options),
...options,
};
startIntegrationsAndBindClient();
}
}
在新版本中,我们将这些值注入到客户端构造函数中。
function init(options) {
const client = new Client({
stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser),
integrations: getIntegrationsToSetup(options),
transport: options.transport || (supportsFetch() ? makeFetchTransport : makeXHRTransport),
...options,
});
startClient(client);
}
这样,用户可以直接使用 Sentry 客户端,并准确地选择他们的应用程序需要的依赖,甩掉不需要的东西。
用魔力字符串摇树
除了允许用户使用摇树功能外,我们还引入了 SDK 范围内的魔力字符串的概念,用户可以通过捆绑器进行配置。配置这些魔力字符串标志可以摇掉较大的的 SDK 功能,同时不需要对 Sentry.init 进行修改。例如,用户可以通过把魔力字符串 SENTRY_DEBUG 设置为 false,从而删除 SD K中的所有 debug 日志逻辑。我们在文档中详细说明了具体配置方法。
const webpack = require("webpack");
module.exports = {
// ... other options
plugins: [
new webpack.DefinePlugin({
__SENTRY_DEBUG__: false,
}),
// ... other plugins
],
};
我们经过了多次迭代,花费了大量的时间才实现了这个功能,尤其是在验证它是否能与不同的捆绑器一起工作时花费了大量心力。
未来我们希望可以引入更多的标志,如果用户不需要某些可选代码时,就可以通过摇树将其消除。欢迎大家提出意见和建议,也可以加入我们的 Discord,里边有一个 JavaScript 频道,大家也可以在 JavaScript SDK 的 GitHub 库另外开一个 issue。
结果
从 Browser JavaScript 7.3.1 版本开始,最小化的未经压缩的浏览器 SDK 的包的大小为 52.67 kb。而 6.16.1 版本的大小为 74.47 kb,这个也就是我们从 12 月开始修改的版本,缩小了 29%。
这些数字是使用大小限制库收集的,大家可以在我们的资料库中看到它的配置。尽管最终结果离最初设定的目标有 1% 的差距,但我们仍然对结果很满意。
在安装了 v7 版的 JavaScript SDK 并启用摇树功能后,我们的 NPM 发行版的用户发回了满意反馈。Next.js SDK 用户报告说,运行时的 JavaScript 减少了 30 kb。我们的内部测试也显示了类似的效果,但具体数字会根据个人使用的具体 SDK 和 SDK 的功能而有所不同。作为提醒,Sentry 支持超过103 个不同平台,所以无论大家使用的是 React、Angular、Vue、Ember、Next.js 还是其他框架,Sentry 都有适合你和你的应用程序的 SDK!
“新的 Sentry JS SDK 让我印象深刻。开箱即用的数据包尺寸明显变小,我们还能通过摇树进一步缩小尺寸。” Shu Ding, 软件工程师,Vercel
v7 发布后,我们收到 0 个确认的 bug 报告,很大程度上是因为我们把重点放在集成测试,同时没有改变公共 API。如果你一直在关注这个把程序包变小的全过程,可以查看这里:程序包太大了!
原文作者:Abhijeet Prasad
原文链接:JavaScript SDK “Package Size is Massive” - So we reduced it by 29%