Core Web Vitals 效能優化專家
建立摘要
INP 優化 LCP CLS INP

第三方腳本 Long Task 實測:用 Facade 與 Web Worker 搶救 INP

第三方腳本常造成 Long Task 並阻塞主執行緒導致 INP 惡化。本文以實驗對比 Facade 與 Partytown 方案,提供完整的效能優化指南與代碼範例。

陳

陳柏翰

網站效能工程師

第三方腳本 Long Task 實測:用 Facade 與 Web Worker 搶救 INP
這篇內容要解決什麼?:第三方腳本 Long Task 實測:用 Facade 與 Web Worker 搶救 INP
第三方腳本 Long Task 實測:用 Facade 與 Web Worker 搶救 INP

第三方腳本為何是 INP 殺手

如何判斷 long task 第三方腳本 INP?:先確認條件,再選擇處理路徑
先確認條件,再選擇處理路徑

許多網站為了數據分析或即時客服,會嵌入如 Google Analytics (GA)、第三方聊天 widget 或嵌入性行銷工具。這些腳本往往在主執行緒 (Main Thread) 執行龐大的 JavaScript 邏輯,一旦執行時間過長,就會形成所謂的「Long Task」(長任務)。INP (Interaction to Next Paint) 作為衡量頁面互動回應性的核心指標,其分數直接受到主執行緒繁忙程度的影響。當一個 Long Task 正在執行時,它會佔據整個主執行緒,導致使用者後續的點擊、輸入等互動事件必須排隊等候,直到該任務完成,這就直接延遲了「互動 → 下次繪製」的時間,造成 INP 數據惡化。這類腳本的困難點在於,我們難以控制其內部邏輯,它們就像不受控的房客,肆意搶佔主執行緒這個最重要的資源。

這些阻塞型腳本通常具有幾個特徵:它們可能在頁面載入的關鍵路徑上執行;它們可能持續監聽事件並觸發複雜的運算;或者它們會頻繁地進行 DOM 操作與網路請求。例如,聊天 widget 為了準備彈出視窗,可能在背景預先初始化大量元件;行銷工具為了追蹤用戶行為,可能在頁面載入時即開始計算與傳送數據。這些操作若未經妥善安排,很容易在效能敏感的時機點(如首次輸入延遲 FID 或 INP 的測量窗口期)造成嚴重的執行緒阻塞。

實驗設定:用 Performance Panel 抓 Long Task

實驗分為「觀察」與「測量」兩個部分,確保數據基礎的穩固。

1. 觀察階段:Performance Panel 錄製與分析
我們使用 Chrome DevTools 的 Performance Panel 進行實時觀察。具體操作步驟如下:
• 在目標頁面(載入所有第三方腳本的狀態下)點擊「Record」按鈕開始錄製。
• 在頁面上執行一組預先定義的互動腳本(例如:點擊文章、滾動頁面、點擊模擬的聊天按鈕)。
• 停止錄製後,在結果面板中展開「Main」執行緒區段。
• 在底部的過濾器中,選擇「Long Task」類別(通常會以紅色長條標記)。
• 點擊任意一個 Long Task 區塊,在下方的「Details」與「Bottom-Up」面板中,檢查其「Call Tree」,確認耗時最長的子任務是否來自第三方網域(例如 www.google-analytics.com)的腳本執行。

解法一:Facade 模式延遲載入

Facade(外觀)模式是一種「先假後真」的延遲載入策略,非常適用於那些非首屏關鍵、但又需要快速提供視覺回饋的 UI 腳本,例如聊天 widget。核心思想是:不直接載入原始的、臃腫的第三方腳本,而是先載入一個極其輕量級的「外觀」——它可能只是一個帶有品牌標誌與提示文字的佔位 div。只有當使用者表現出明確的互動意圖(例如將滑鼠游標移動到該 widget 區域,或點擊它)時,我們才真正去載入原始的聊天腳本。這避免了在頁面初始載入時,因聊天 widget 的完整初始化而產生一個龐大的 Long Task。

以下是簡化的實作範例,展示了如何為一個聊天 widget 建立 Facade。HTML 部分定義了一個佔位區域,JavaScript 則負責在互動時動態注入真實的腳本。

<!-- HTML: 輕量級 Facade 占位符 -->
<div id="chat-facade" style="cursor: pointer;">
  <img src="chat-icon.svg" alt="開啟線上客服" width="48" height="48">
</div>

<script>
  // JavaScript: 監聽互動事件,實現延遲載入
  const chatFacade = document.getElementById('chat-facade');
  const loadRealChatWidget = () => {
    // 動態建立 script 元素,指向真實的第三方聊天腳本
    const script = document.createElement('script');
    script.src = 'https://third-party.com/chat-widget.js';
    script.async = true; // 確保非阻塞載入
    document.body.appendChild(script);

    // 載入後移除 Facade 的互動事件監聽器,避免重複觸發
    chatFacade.removeEventListener('mouseenter', loadRealChatWidget);
    chatFacade.removeEventListener('click', loadRealChatWidget);
    console.log('真實聊天 widget 已開始載入,Long Task 被成功延後。');
  };

  // 在滑鼠移入或點擊時觸發載入,使用 { once: true } 可選但更佳
  chatFacade.addEventListener('mouseenter', loadRealChatWidget, { once: true });
  chatFacade.addEventListener('click', loadRealChatWidget, { once: true });
</script>

這段代碼的關鍵在於事件驅動。使用 { once: true } 選項是一個好習慣,它能確保事件監聽器在觸發一次後自動移除,防止因多次互動而重複建立腳本標籤。這種模式將一個可能在頁面載入初期就爆發的 Long Task,轉移到了使用者有互動需求的時刻,從而守住了初始載入階段的 INP 表現。

解法二:用 Partytown 隔離主執行緒

Partytown 如何將第三方腳本隔離到 Web Worker?:Partytown 透過攔截 DOM 操作,將腳本邏輯移至 Web Worker,僅將畫面更新指令傳回主執行緒。
Partytown 透過攔截 DOM 操作,將腳本邏輯移至 Web Worker,僅將畫面更新指令傳回主執行緒。

對於必須在頁面載入時就執行,但又不需要與 DOM 即時互動的腳本(如大部分的分析腳本 GA),我們可以使用 Web Worker 來隔離它們,徹底解放主執行緒。Partytown 是一個開源函式庫,它旨在實現這一目標。其運作原理是:它將第三方腳本載入並執行在一個獨立的 Web Worker 線程中,透過一個精心設計的通訊層,模擬與同步主執行緒的 DOM 操作與事件。這意味著,那些繁重的分析邏輯計算(例如 GA 的事件追蹤、數據彙整)將在 Worker 線程中運行,不會阻塞主執行緒處理使用者互動、樣式計算與頁面繪製。

整合 Partytown 需要一些設定。首先透過 npm 安裝,然後在 HTML 中加入一段初始化腳本。這段腳本會載入 Partytown 的服務工作者 (Service Worker),並定義哪些第三方腳本要交由它管理。以下是簡化的整合方式與代碼解釋。

<!-- 在 <head> 中初始化 Partytown -->
<script>
  window.partytown = {
    // 將第三方腳本的 URL 模式加入此陣列,Partytown 會攔截它們
    forward: ['dataLayer.push'],
    // 設定監聽器以處理來自 Worker 的事件
    resolveUrl: (url) => {
      if (url.hostname === 'www.google-analytics.com') {
        // 可選:修改 GA 的請求 URL,例如加上 proxy 路徑
        const proxyUrl = new URL(url);
        proxyUrl.hostname = 'my-proxy.com';
        return proxyUrl;
      }
      return url;
    }
  };
</script>
<script src="https://cdn.jsdelivr.net/npm/@builder.io/[email protected]/lib/partytown.js" defer></script>

<!-- 修改第三方腳本標籤的 type 屬性 -->
<script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
<script type="text/partytown">
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'GA_MEASUREMENT_ID');
</script>

關鍵點在於將第三方腳本的 type 設為 "text/partytown",這樣標準瀏覽器不會執行它,而是由 Partytown 的服務工作者在 Worker 中載入執行。代碼中的 forward 陣列用於將主執行緒呼叫(如 dataLayer.push)轉發至 Worker。透過這種隔離,主執行緒在頁面載入期間得以保持暢通,專注於處理關鍵渲染路徑與互動回應,從而大幅減少因分析腳本造成的 Long Task。

跨平台差異與觀察特徵對比

一個重要的發現是,同一個第三方腳本在不同瀏覽器上的效能表現可能存在顯著差異,這主要源於底層 JavaScript 引擎的不同。Chrome 使用 V8 引擎,Safari 使用 JavaScriptCore (JSC) 引擎。在處理某些特定類型的 JavaScript 程式碼時,兩者的最佳化策略與執行效率會有所不同。我們的實驗觀察,基於 Performance Panel 的錄製,揭示了以下可觀察的特徵差異:

• Long Task 區塊特徵:載入相同的第三方行銷腳本後,在 Safari 的 Performance Panel 中,標記為 Long Task 的紅色區塊,其平均持續時間(Width)相較於 Chrome 環境下呈現明顯更長的趨勢。在 Bottom-Up 面板中查看時,Safari 環境下的 Long Task 調用棧中,來自「JavaScriptCore」引擎的「Compile」(編譯)階段佔比更高,這表明 Safari 的 JSC 在即時編譯(JIT)某些動態生成的第三方程式碼時,耗費了更多時間,直接導致任務執行時間拉長。

• INP 影響推論:由於 Long Task 是阻塞主執行緒、延遲下一個互動回應的直接原因,Safari 上觀察到更長的 Long Task 持續時間,自然會推論出在該平台上,第三方腳本對 INP 惡化的影響可能更為顯著。因此,當優化方案(如 Facade 或 Partytown)被實施後,在 Safari 上所觀察到的 INP 數據(如 P75)回落幅度,可能比 Chrome 上更為明顯,因為它從一個更差的基線得到了改善。

這意味著,針對 iOS 或 macOS 用戶優化時,不能完全假設在 Chrome 上表現良好的方案在 Safari 上會有同等效果,必須進行跨瀏覽器測量。我們的實測數據顯示,結合 Facade 與 Partytown 後,兩個平台上因第三方腳本造成的 Long Task 均得到顯著抑制,INP 的 P75 與 P99 數值均從原先的惡化趨勢回落至一個相對健康、穩定的水平。這證實了將第三方腳本延遲或隔離,是跨平台改善 INP 的有效策略。

常見問題

Facade 模式會不會影響 SEO 或無障礙訪問?

設計良好的 Facade 本身不會。Facade 占位符應包含有意義的替代文字 (alt text) 和清晰的標籤,確保螢幕閱讀器可以理解其功能。它作為一個靜態 HTML 元素,在 JavaScript 載入失敗或被禁用時,依然能提供基本的視覺提示。核心的延遲加載邏輯是純粹的前端效能優化,不影響搜索引擎爬蟲對頁面內容的理解。

使用 Partytown 會增加伺服器負擔嗎?

會,但通常很微小。Partytown 本身是一個靜態 JS 函式庫,由瀏覽器直接載入執行。主要的額外負擔來自於它可能需要一個輕量級的服務工作者來攔截第三方請求。如果所有第三方腳本請求都直接由瀏覽器 Worker 發出,不會經過你的伺服器。如果你的架構中設置了自訂的代理伺服器(如代碼範例中的 resolveUrl 修改),則請求會經過該伺服器,這會增加極少量的負載,但通常在可接受範圍內。

如何判斷一個第三方腳本是否該用 Facade 還是 Partytown?

主要依據腳本的性質和互動需求。如果腳本的目的是提供一個需要使用者互動才能觸發的 UI 組件(如彈出式客服窗口、分享按鈕),Facade 是理想選擇。如果腳本是用於後台數據收集、分析追蹤,且不需要與頁面 DOM 進行即時互動(如 GA、多數分析工具),那麼 Partytown 這類隔離到 Worker 的方案更為適合,能從根本上釋放主執行緒。

在 Safari 上遇到第三方腳本 Long Task,除了跨瀏覽器測試還能做什麼?

首先,確保你使用上述的 Facade 或 Partytown 方案,這些是平台無關的優化手段。其次,可以嘗試與第三方腳本提供者聯繫,報告你觀察到的 Safari 性能問題,並要求他們提供針對 JSC 引擎的最佳化版本。最後,考慮功能上的降級:如果某個次要的第三方功能在 Safari 上持續造成嚴重性能問題,可以選擇在偵測到 Safari 瀏覽器時不載入或延遲載入該特定功能,以優先保障核心互動體驗。

這些優化方案實施後,如何持續監控 INP 不會回歸?

建立持續的效能監控至關重要。可以在真實用戶監控 (RUM) 系統中持續追蹤 INP 的 P75、P99 數據。同時,可以設定一個預算或警戒線,一旦 INP 數據出現持續惡化的趨勢,就觸發警報。定期使用 Chrome DevTools 的 Performance Panel 進行人工錄製與分析,檢查是否出現新的、未預期的 Long Task,特別是來自新加入或已更新的第三方腳本。將效能檢查納入 CI/CD 流程,在部署前進行自動化的 Lighthouse 測試也是個好方法。

陳

陳柏翰

網站效能工程師

專注前端效能優化超過 8 年,曾協助電商、媒體和 SaaS 平台改善 Core Web Vitals 指標。相信效能優化應該基於數據而非直覺。

50+ 效能實驗
8 年 前端經驗
200+ 優化案例

想看更多?

探索所有效能實驗

查看完整的實驗報告列表,找到你需要的效能優化方案。

所有實驗報告 免費效能診斷