Partytown 🎉 介绍:从 Web Worker 运行第三方脚本
第三方脚本的有趣存放地点
对于任何网站或 Web 应用来说,性能始终是首要考虑因素。一个能够快速加载、无滚动卡顿、并能立即响应任何交互的页面,无疑能够提供更佳的用户体验。
即使您的网站速度快、高度优化,并遵循所有最佳实践,但一旦添加第三方脚本,您之前获得的性能优势就可能荡然无存,这种情况屡见不鲜。我们所说的第三方脚本是指嵌入到您的网站中,但并非由您直接控制的代码。例如:分析、广告像素、A/B 测试、跟踪器等。
当谈到提高网站性能时,资源通常会解释并记录您可以对代码进行的切实改进,但在大多数情况下,当谈到改进第三方代码时,我们却无能为力。
第三方脚本性能问题
一个显而易见的事实是,第三方脚本常常会消耗大量主线程的宝贵资源。有一些技巧可以减少它们的前期破坏性影响,比如等到页面加载完成后再运行这些脚本。
但无论如何,它们仍然在用户主线程上运行着数百 KB(通常甚至几 MB)的 JavaScript!而且,终端用户的移动设备资源比开发人员构建网站的机器还要少!这会严重影响Lighthouse 评分、核心网络指标、搜索排名,甚至会因为用户体验不佳而增加跳出率并降低用户参与度。
所有这些都是在我们为Builder.io构建Qwik时浮现出来的。简而言之,我们可以仅使用 HTML 和 CSS 让交互式网站立即加载,并且只按需加载所需的 JavaScript。但无论如何,即使使用最快的框架(或者根本不使用框架),第三方脚本仍然会消耗网站性能。所以我们开始思考……
在 Web Worker 中运行第三方脚本
Partytown 的理念是,主线程应该专用于你的代码,所有不需要位于关键路径的脚本都应该迁移到Web Worker中。迁移到一个沙盒环境中,有点像……第三方脚本的小镇。某种意义上……Partytown,如果你愿意的话……
多年来,Web Worker一直是一种实用的解决方案,可以将资源密集型任务从主线程中卸载。然而,挑战在于 Worker 无法直接访问主线程 API,例如window
、document
或localStorage
。虽然可以在两个线程之间创建消息系统,但由于postMessage是异步的,因此第三方脚本中充斥的 DOM 操作在传统的消息系统中根本无法成功执行。
例如,以下是在Google Tag Manager中找到的一段代码:
var w = document.body.clientWidth;
这段代码没什么特别的,实际上它非常常见。但是,请注意它必须是同步的,并且有三个阻塞的 getter:
- 得到
document
- 得到
body
- 得到
clientWidth
如果我们不能重构这段代码,改用 Promise 或回调,那么异步消息系统就无法让它“正常工作”。我想强调的是,“无法重构这段代码”。
就在你读到这些文字的时候,数十亿设备正在执行的第三方脚本,根本无法被“重构”。理想情况下,我会给谷歌发信息说:“嘿,你知道那段耗资数十亿美元的分析代码吗?请彻底重构它。谢谢。” 接下来,我还得给世界上的每一项服务都发私信,让他们也重构代码。祝我好运吧,但结果可能有所不同。
带我去派对小镇
Partytown是一个延迟加载6kb
库,可帮助将资源密集型脚本从主线程移至Web Worker中。其目标是通过将主线程专用于代码并将第三方脚本卸载到 Web Worker 来帮助加快网站速度。
但是,它带来的最重要的一点是允许 Web Worker从主线程同步读取数据。如果 Web Worker 中运行的代码可以调用具有同步返回值的阻塞 DOM API,那么这意味着我们可以在 Worker 中运行未经修改的第三方脚本。第三方代码会按预期顺利执行,但会在不同的线程中执行,从而不会占用代码的资源。
沙盒和隔离
第三方脚本通常是一个包含大量 JavaScript 代码的黑盒。这些混淆代码中隐藏的内容很难辨别。虽然压缩代码是有充分理由的,但无论如何,要理解哪些第三方脚本正在您的网站和用户的设备上执行,以及它们是否与您的应用代码在同一个线程/上下文中执行,都会变得非常困难。
另一方面,Partytown能够在 Web Worker 中对第三方脚本进行沙盒隔离,并允许或拒绝访问主线程 API。这些 API 包括 Cookie、localStorage、userAgent 等。由于代码必须通过 Partytown 的代理才能访问主线程,因此 Partytown 还可以记录每次读写操作,甚至限制对某些 DOM API 的访问。
本质上,Partytown 可以让你:
- 在沙箱内隔离第三方脚本。
- 配置哪些浏览器 API 特定脚本可以执行以及不能执行。
- 选择记录 API 调用和参数,以便更好地了解脚本正在执行的操作。
这对于许多不同的用例都很有用,包括:
- 阻止访问
document.cookie
- 提供标准
navigator.userAgent
- 不允许脚本写入
localStorage
- 变成
document.write()
一个noop
函数 - 阻止脚本请求其他脚本
现状和下一步计划
Partytown 仍处于 Alpha 测试阶段,高度实验性,尚未准备好投入生产。不过,我们已经在 Builder.io 上的生产网站上的几个页面上积极测试了它,目前一切顺利。数据收集工作按预期进行,我们的分析似乎没有受到影响。我们的目标是现在收集数据,以便在以后的文章中展示。
在下一篇文章中,我将重点介绍同步通信通道的工作原理及其一些权衡。
此外,我们将展示如何在 React 或Next.js项目,甚至任何网站或 Web 应用中测试 Partytown。以下是一个简单的示例,展示了如何在 Next.js 文档中使用 Partytown,更多内容将在后续文章中介绍:
import { Partytown, GoogleTagManager } from '@builder.io/partytown/react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
export default class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<GoogleTagManager containerId={'GTM-XXXXX'} />
<Partytown />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
如果您想了解更多信息,甚至想帮忙测试,欢迎加入我们的Discord 频道,或通过@adamdbradley联系我。我很乐意确保 Partytown 能够与您的服务或用例兼容,所以请随时与我们聊天。
我还要感谢一些很棒的人,我们有幸与他们交流想法,并帮助验证这是否可在现实生活中行得通:Addy Osmani、Ilya Grigorik、Kristofer Baxter、Shubhie Panicker、Zach Leatherman、Misko Hevery、Steve Sewell和整个Builder.io 团队。
继续派对吧,韦恩!
文章来源:https://dev.to/adamdbradley/introducing-partytown-run-third-party-scripts-from-a-web-worker-2cnp