軟體包優化是指決定哪些符號放入哪些軟體包的過程,以便應用程式可以將一起使用的程式碼放在一起。將符號放在一起可以使應用程式載入速度更快。

符號 vs 區塊

符號是 Qwik 中個別可懶載入的片段。每當您在原始碼中使用 __$(__) 時,就會建立一個符號。

例如,以下程式碼會從 component$onClick$ 建立兩個符號。

import { component$, useSignal } from '@builder.io/qwik';
 
export default component$(() => {
  const count = useSignal(0);
 
  return (
    <button onClick$={() => count.value++}>
      Increment {count.value}
    </button>
  );
});

優化器會將上述程式碼改寫成類似這樣

檔案:chunk-1.js

export default componentQRL(qrl('./chunk-1.js', 's_ABC123'));
 
export const s_ABC123 = () => {
  const count = useSignal(0);
 
  return (
    <button onClickQRL={qrl('/.chunk-1.js', 's_XYZ342')}>
      Increment {count.value}
    </button>
  );
};
 
export const s_XYZ432 = () => {
  const [count] = useLexicalScope();
  return count.value++;
}

在上面的例子中,所有的符號 (sABC123s_XYZ432) 都位於同一個區塊 (./chunk-1.js) 中。

區塊是可以包含一個或多個符號的 JavaScript 套件。

最佳符號分配

您可以將套件優化視為一個滑動條,讓我們可以優化符號的傳遞。

  • 在滑動條的一端,我們有一個包含所有符號的單一區塊。這相當於沒有任何懶載入的應用程式。(這是現今大多數應用程式的撰寫方式。)
  • 在滑動條的另一端,我們為每個符號都有一個獨立的區塊。這就是 Qwik 應用程式在開發過程中的行為方式。每個符號都在自己的區塊中。

單一區塊的問題是

  • 它將包含許多用戶端不需要的符號。(浪費頻寬。)
  • 在整個區塊載入之前,用戶端無法執行任何符號。

每個符號使用獨立區塊的問題是

  • 用戶端必須發出許多請求才能載入所有區塊,這通常會導致不必要的瀑布式請求。

最佳解決方案介於兩者之間。我們希望區塊數量少,但也希望將一起使用的符號放在同一個區塊中。區塊數量少讓我們可以優先考慮區塊載入的順序,同時攤銷發出 HTTP 請求的成本。將符號放在一起可以最大程度地減少瀑布式請求。

好消息是,使用 Qwik,您可以完全控制哪些符號進入哪些區塊。通常,為了懶載入而拆分應用程式需要開發人員撰寫動態導入並重構程式碼。在 Qwik 中,所有 $() 都是潛在的懶載入位置,您只需要告知套件管理器如何分配符號。

qwikVite() 外掛

vite.config.ts 檔案中的 qwikVite() 外掛控制符號分配。通常,entryStrategy 設定為 smart,這讓 Qwik 可以啟發式地猜測如何懶載入符號。但是,也可以透過在 vite.config.ts 檔案中提供如下所示的 manual 設定來覆蓋啟發式方法

export default defineConfig(() => {
  const routesDir = resolve('src', 'routes');
  return {
    // ...
    qwikVite({
      entryStrategy: {
        type: 'smart',
        manual: {
          ...bundle('bundleA', [
              's_I5CyQjO9FjQ',
              's_NsnidK2eXPg',
              's_kDw0latGeM0',
          ]),
          ...bundle('bundleB', [
              's_vXb90XKAnjE',
              's_hYpp40gCb60',
          ]),
          ...bundle('bundleC', [
              's_AqHBIVNKf34',
              's_oEksvFPgMEM',
              's_eePwnt3YTI8',
          ]),
        },
      },
    }),
  };
});
 
function bundle(bundleName: string, symbols: string[]) {
  return symbols.reduce((obj, key) => {
    // Sometimes symbols are prefixed with `s_`, remove it.
    obj[key.replace('s_', '')] = obj[key] = bundleName;
    return obj;
  }, {} as Record<string, string>);
}

所以問題就變成如何取得像是 s_I5CyQjO9FjQ 這樣的符號名稱?請參閱下一節「執行時分析」。

執行時分析

要解決的根本問題是,無法靜態地確定最佳套件。理想的套件取決於使用者的行為。只有在觀察了使用者的行為之後,我們才能確定哪些符號會一起使用。

若要從執行中的應用程式收集符號使用情況,請執行以下操作

  1. 將此程式碼插入您的應用程式中
    <script>
      window.symbols = [];
      document.addEventListener('qsymbol', (e) => window.symbols.push(e.detail));
    </script>
  2. 執行一些模擬使用者行為的操作。
  3. 開啟控制台並輸入 symbols 以查看已使用符號的清單。使用該資訊更新 vite.config.ts 檔案。

**注意:**我們正在研究未來建立更好的資訊收集方式。(請參閱分析。)

注意:符號雜湊值設計為即使在多次編譯後也能保持穩定。但是,如果程式碼經歷了複雜的重構,則雜湊值可能會發生變化。這不會破壞應用程式,但可能會導致符號移動到另一個非最佳區塊,直到再次收集到執行時分析數據為止。

服務工作線程

Qwik 應用程式使用服務工作線程來確保套件被預先擷取到瀏覽器的快取中,並且任何使用者互動都會導致快取命中,因此互動不會延遲。

請參閱預測性模組提取

請注意,服務工作線程功能僅在安全環境(HTTPS)中可用,在某些或所有支援的瀏覽器中皆可使用。請參閱serviceWorker 屬性 API 規範

事件

所有關於符號何時載入的資訊都可以通過監聽以下自訂事件來觀察

qprefetch 自訂事件詳細資訊。

當新的應用程式視圖呈現給使用者時,會觸發 qprefetch 事件。(例如,呈現新的模型對話方塊將會有一個新的按鈕。我們希望確保新的按鈕程式碼被預先擷取,以便當使用者與按鈕互動時不會有任何延遲。)通常,服務工作線程會監聽 qprefetch 事件,並將符號載入快取中。服務工作線程有一個符號到套件的映射,它使用這些資訊來根據符號確定要預先擷取哪些套件。

export interface QPrefetchDetail {
  /// A list of symbols to prefetch.
  symbols: string[];
}

qsymbol 自訂事件詳細資訊。

每次 Qwik 需要解析符號時,都會觸發 qsymbol 事件。監聽此事件可以讓您深入了解應用程式何時載入不同的符號。然後,可以使用這些資訊通過將需要的符號放在同一個套件中來更好地優化套件。

export interface QSymbolDetail {
  /// Optional DOM event which triggered the symbol resolution.
  element?: Element;
  /// Request time when the symbol was resolved.
  reqTime: number;
  /// Symbol being resolved.
  symbol: string;
}

瀑布流

服務工作線程嘗試通過預先擷取套件來最小化瀑布流。為了做到這一點,服務工作線程有一個符號和區塊的 manifestmanifest 代表所有符號及其對應區塊的完整圖形。它還了解匯入圖,因此如果預先擷取了符號,服務工作線程也將預先擷取作為匯入圖一部分所需的所有其他符號。

貢獻者

感謝所有幫助改進此文檔的貢獻者!

  • mhevery
  • the-r3aper7
  • mrhoodz
  • Craiqser
  • literalpie
  • antoinepairet
  • hamatoyogi