Partytown 如何消除第三方脚本所带来的网站膨胀

出色的用户体验始于快速响应加载的页面,通常用户不会花太多时间等待网页加载或与页面交互:根据谷歌的说法,如果页面的加载时间从 1 秒增加到 3 秒,用户跳出的概率会增加 32 %。然而,在网站中保持高性能并不总是那么容易。访问网站的速度快慢在某种程度上也是一种歧视:因为世界上大多数人可能都无法高速访问互联网甚至无法拥有快速的CPU。即使您的网站在设计时考虑了可用性,这些因素也会阻碍用户充分受益于网站的功能。

这就是为什么在构建网站时性能至关重要。

性能需要从代码级别开始构建,以用户为中心的指标,如交互时间 (TTI)、总阻塞时间 (TBT) 和首次输入延迟 (FID) 等指标可帮助您衡量网站的速度。但是现代网页很重并且尺寸还在不断增长(我们亲切地称之为“网站膨胀”):平均网页超过 2 兆字节,有超过 200 个请求。许多大型、笨重的网站,嵌入了多个第三方脚本,这通常会导致令人沮丧的用户体验。就像大多数网站一样,当您需要网站上的这些第三方脚本来运行业务时,就会面临着巨大的挑战:

如何在不影响重要功能的情况下改进关键性能指标并让用户满意?

JavaScript的负担

众所周知, JavaScript 是网站膨胀的罪魁祸首之一,丰富的交互式网站体验需要消耗从 CPU 和 GPU 到内存和网络等用户资源。除了大图像和视频之外,还有像素跟踪器、A/B 测试、广告、小部件、CDN 等第三方脚本通常是性能难题中最大的一块。第三方脚本是嵌入在您的网站中且不受开发人员直接控制的代码,它们与网站自身的代码竞争浏览器的主线程,这无疑会延迟内容呈现并使网站感觉迟缓。(这会对Lighthouse 分数、Core Web Vitals和搜索排名产生负面影响,并降低用户参与度)

根据 Google Web Fundamentals 的说法,第三方脚本可能会导致几个问题,包括:

  • 对多个服务器的网络请求过多;
  • 发送过多的 JavaScript;
  • 资源密集型脚本解析和执行;
  • HTTP 缓存不足;
  • 缺乏足够的服务器资源压缩;
  • 阻止内容显示,直到它们完成处理;
  • 使用已知对用户体验有害的遗留 API(例如 document.write()
  • 过多的 DOM 元素或耗性能的 CSS 选择器。

当网页上有大量第三方脚本需要加载时,它们会阻止或限制网站自身的 JavaScript。一方面这对于时间就是金钱的电子商务网站和在线市场来说无疑是灾难,但另一方面它们又需要这些第三方脚本来运行业务,这尤其重要。

针对这一系列问题,将第三方脚本交给在后台线程中运行的 Web Worker 来执行是一种潜在的解决方案,它允许用户在提高性能的同时保留他们的脚本。但 Web Worker 只能与主线程异步通信,且不能直接访问 DOM(只有主线程可以)。因此,关键的挑战在于——为 Web worker 中运行的 JavaScript 代码提供对 DOM 的某种访问方法,并使该访问方法能够同步执行(即使与主线程的通信之间必须保持异步)。

正式介绍 Partytown

Partytown 是一个轻量级的开源解决方案,通过将第三方脚本卸载到在后台线程中运行的 Web Worker 中执行来减少由第三方 JavaScript 导致的延迟问题。这释放了浏览器的主线程来运行网站自己本身的代码。它由 Builder.io 维护,目前处于测试阶段。

Builder.io 同样也在致力于 Qwik 的维护,Qwik 是一个开源的 HTML 优先、可恢复的 Web 应用程序框架,它使交互式网站仅使用 HTMLCSS 即可快速加载,仅在需要时提取 JavaScript

虽然 Partytown 并没有完全解决由第三方脚本引起的所有瓶颈(在上面的部分中列出),但它确实通过以下方式解决了构建高性能网站的最大挑战:

  • 释放主线程资源,仅用于主要的 Web 应用程序执行;
  • 沙盒化第三方脚本并允许或拒绝他们访问主线程 API
  • 在 web worker 线程中隔离长时间运行的任务;
  • 通过 DOM setter/getter 进行批量一次更新来减少第三方脚本产生的布局多次抖动;
  • 限制第三方脚本对主线程的访问;
  • 允许第三方脚本完全按照它们的编码方式运行,无需任何更改;
  • 从 web worker 中同步读取和写入主线程 DOM 操作,允许从 web worker 运行的脚本按预期执行。

Partytown 背后的体系结构

尽管各种前沿创新技术在不断加快我们向浏览器提供的 JavaScript 代码的速度(包括但不限于压缩、分发、代码拆分、异步、延迟),但只要代码在浏览器中执行,就会受到 JavaScript单线程语言这一事实的限制。

Partytown 作为一个延迟加载的 JavaScript 库,将资源密集型脚本重定向到 Web Worker 中是它的使命。为了确保各种规模的应用程序都可以继续使用第三方脚本而不会遇到性能障碍,Partytown 将这些第三方脚本卸载到 Web Worker 中,并且让允许或拒绝他们访问主线程 API成为可控项。换句话说,不需要在关键渲染路径中的第三方脚本即可在后台线程中执行,从而释放了浏览器的主线程来执行第一方 JavaScript:比如那些负责处理用户输入或绘制屏幕等影响直观体验的事件。

Google Analytics (谷歌分析插件)为例,它使用 navigator.sendBeacon() 收集和发送跟踪数据。一方面,这是一个可以异步运行的后台任务,另一方面,当从 documentwindow 读取值时,Google Analytics 仍然需要同步 DOM API 访问。于是 Partytown 允许在后台执行诸如 Google Analytics 之类的脚本,让他们能同时访问 DOM,就好像它在主线程中一样。

Partytown 如何消除第三方脚本所带来的网站膨胀

Partytown 是如何运作的

Web Worker 的主要挑战是它无法直接访问可从主线程访问的 DOM API,例如 window, document(其实也包括BOM)或 localStorage 等。虽然可以在两个线程之间创建消息系统来代理 DOM 操作,但 postMessage 用于 Web worker 主线程通信的 API 是异步的,这意味着依赖于同步 DOM 操作的第三方脚本只会失败。

Partytown 通过使用 JavaScript 代理、service worker 和同步 XHR 请求从 web worker 内部提供对 DOM API 的同步访问。使用正在访问的方法和值(例如,document.titlewindow.screen.width)创建同步 XHR 请求,代理访问 Web Worker 中的 DOM API

这些请求被 Web Worker 拦截,然后用 postMessage 将 API 请求异步中继到主线程,通过将每个 DOM API 请求映射到同步 XHRweb worker 直到 service worker 响应前总会暂停执行。最终的结果是,从 web worker 中的第三方脚本来看,都是同步的。

这种方法的好处是你不需要重写或重构你的第三方脚本来让它们在 web worker 中工作。它们会完全按照编码顺序执行,而且是在后台线程中。

此外,通过代理所有 DOM API 访问,Partytown 可以记录每次读取和写入,甚至精确限制对某些 DOM API 的访问。

设置 Partytown

Partytown 不会自动将所有第三方脚本移至网络工作者,开发人员需要选择性加入脚本来开始运作,也就是说,开发者必须选择要通过 Partytown 来加载和执行哪些脚本。

npm 在命令行中使用来安装 Partytown。

npm install @builder.io/partytown

接下来,将 type="text/partytown" 属性添加到要从 Web Worker 运行的每个第三方脚本。

- <script>...</script>
+ <script type="text/partytown">...</script>

type="text/partytown" 仅当特定脚本具有该属性时,才启用 Partytown 。这做了两件事:

  1. 阻止主线程执行脚本;
  2. Partytown 提供查询选择器,如 document.querySelectorAll('').

下一步是修改 <head>如果您使用的是 React,建议使用 <Partytown/> React 组件)。

以下是<Partytown/>Next.js 页面中包含该组件的示例。

import Head from "next/head";
import { Partytown } from "@builder.io/partytown/react";
const Home = () => {
  return (
    <>
      <Head>
        <title>My App</title>
        <script type="text/partytown" src="https://example.com/analytics.js"></script>
        <Partytown />
      </Head>
      <main>...</main>
    </>
  );
};
export default Home;

Partytown 适用于任何 HTML 页面,不需要特定的框架,但有一些集成(插件/包装器)可用,包括 Next.jsNuxt.jsReactShopify HydrogenPartytown 还为 Facebook PixelAdobe LaunchGoogle Tag Manager 等一些第三方服务提供文档和演示。

在设置 Partytown 时,首先在几个页面上尝试并使用 Google PageSpeed Insights 衡量改进结果,确认所有脚本都正常工作后,才可以为网站上的所有页面打开它。

Partytown 实践成果

Builder.io 网站通过结合使用 PartytownQwik 成功削减了99% 的 JavaScript。这显着提高了性能,即使在移动设备上,PageSpeed Insights 的 Google Lighthouse 得分也达到了100 ⁄ 100 。总阻塞时间 (TBT) 和交互时间 (TTI) 也大幅减少,这是衡量第三方脚本延迟第一方 JavaScript 执行时间的指标。

开发者共赴Party吧

Partytown 仍处于测试阶段,因此并非每个脚本都有效。Builder.io 积极邀请大家测试 Partytown 并与团队分享他们的想法。用户可以在Partytown 的 GitHub 上报告问题、请求集成和演示成果,或贡献代码。Partytown 还有一个活跃的Discord 社区,您可以在其中帮助测试 Partytown 并加入对话。

Builder.io 的宗旨是使高性能成为网站的标准配置:可以在没有任何配置的情况下实现一个具有最佳性能的快速网站。Builder 通过其开源解决方案 PartytownQwik 朝着这一理想迈出了第一步,对于让任何人都可以访问接近零延迟响应网站的想法,这两个解决方案都至关重要。

原文链接: How Partytown Eliminates Website Bloat From Third-Party Scripts