跳至內容

外掛的環境 API

實驗性功能

環境 API 屬於實驗性功能。我們將在 Vite 6 中保持 API 的穩定性,讓生態系統可以進行實驗並在其基礎上建構。我們計劃在 Vite 7 中穩定這些新的 API,並可能進行重大變更。

資源

請與我們分享您的意見回饋。

在鉤子中存取目前的環境

在 Vite 6 之前,只有兩種環境(clientssr),因此 ssr 布林值足以識別 Vite API 中的目前環境。外掛鉤子在最後的選項參數中收到 ssr 布林值,而數個 API 預期會有選填的最後一個 ssr 參數,以將模組正確地關聯到正確的環境(例如 server.moduleGraph.getModuleByUrl(url, { ssr }))。

隨著可設定環境的出現,我們現在可以使用統一的方式在外掛中存取其選項和實例。外掛鉤子現在在其上下文中公開 this.environment,而先前預期會有 ssr 布林值的 API 現在已限定到適當的環境(例如 environment.moduleGraph.getModuleByUrl(url))。

Vite 伺服器具有共用的外掛管道,但是當處理模組時,始終是在給定環境的上下文中進行。environment 實例在外掛的上下文中可用。

外掛可以使用 environment 實例,根據環境的設定(可以使用 environment.config 存取)來變更處理模組的方式。

ts
  transform(code, id) {
    console.log(this.environment.config.resolve.conditions)
  }

使用鉤子註冊新環境

外掛可以在 config 鉤子中新增新環境(例如,為 RSC 建立單獨的模組圖)。

ts
  config(config: UserConfig) {
    config.environments.rsc ??= {}
  }

空物件足以註冊環境,以及來自根層級環境設定的預設值。

使用鉤子設定環境

config 鉤子執行時,尚未知道完整的環境清單,而且環境可能會受到來自根層級環境設定的預設值,或是透過 config.environments 記錄明確地影響。外掛應該使用 config 鉤子設定預設值。為了設定每個環境,它們可以使用新的 configEnvironment 鉤子。會針對每個環境呼叫此鉤子,並帶有其部分解析的設定,包括最終預設值的解析。

ts
  configEnvironment(name: string, options: EnvironmentOptions) {
    if (name === 'rsc') {
      options.resolve.conditions = // ...

hotUpdate 鉤子

  • 類型: (this: { environment: DevEnvironment }, options: HotUpdateOptions) => Array<EnvironmentModuleNode> | void | Promise<Array<EnvironmentModuleNode> | void>
  • 另請參閱: HMR API

hotUpdate 鉤子允許外掛對給定環境執行自訂 HMR 更新處理。當檔案變更時,會依據 server.environments 中的順序,依序針對每個環境執行 HMR 演算法,因此會多次呼叫 hotUpdate 鉤子。此鉤子會收到具有下列簽章的上下文物件

ts
interface HotUpdateOptions {
  type: 'create' | 'update' | 'delete'
  file: string
  timestamp: number
  modules: Array<EnvironmentModuleNode>
  read: () => string | Promise<string>
  server: ViteDevServer
}
  • this.environment 是目前正在處理檔案更新的模組執行環境。

  • modules 是此環境中受變更檔案影響的模組陣列。它是陣列,因為單一檔案可能會對應到多個服務的模組(例如 Vue SFC)。

  • read 是傳回檔案內容的非同步讀取函式。提供此函式的原因是,在某些系統上,檔案變更回呼可能會在編輯器完成更新檔案之前太快觸發,而直接的 fs.readFile 將會傳回空的內容。傳入的讀取函式會標準化此行為。

此鉤子可以選擇

  • 篩選並縮小受影響的模組清單,以便 HMR 更準確。

  • 傳回空陣列並執行完整重新載入

    js
    hotUpdate({ modules, timestamp }) {
      if (this.environment.name !== 'client')
        return
    
      // Invalidate modules manually
      const invalidatedModules = new Set()
      for (const mod of modules) {
        this.environment.moduleGraph.invalidateModule(
          mod,
          invalidatedModules,
          timestamp,
          true
        )
      }
      this.environment.hot.send({ type: 'full-reload' })
      return []
    }
  • 傳回空陣列並透過傳送自訂事件到用戶端來執行完整的自訂 HMR 處理

    js
    hotUpdate() {
      if (this.environment.name !== 'client')
        return
    
      this.environment.hot.send({
        type: 'custom',
        event: 'special-update',
        data: {}
      })
      return []
    }

    用戶端程式碼應該使用 HMR API 來註冊對應的處理常式(這可以由相同外掛的 transform 鉤子注入)

    js
    if (import.meta.hot) {
      import.meta.hot.on('special-update', (data) => {
        // perform custom update
      })
    }

每個環境的外掛

外掛可以使用 applyToEnvironment 函式來定義它應該套用至哪些環境。

js
const UnoCssPlugin = () => {
  // shared global state
  return {
    buildStart() {
      // init per environment state with WeakMap<Environment,Data>
      // using this.environment
    },
    configureServer() {
      // use global hooks normally
    },
    applyToEnvironment(environment) {
      // return true if this plugin should be active in this environment,
      // or return a new plugin to replace it.
      // if the hook is not used, the plugin is active in all environments
    },
    resolveId(id, importer) {
      // only called for environments this plugin apply to
    },
  }
}

如果外掛沒有環境意識,而且其狀態未以目前的環境做為索引鍵,則 applyToEnvironment 鉤子允許輕鬆地使其成為每個環境的外掛。

js
import { nonShareablePlugin } from 'non-shareable-plugin'

export default defineConfig({
  plugins: [
    {
      name: 'per-environment-plugin',
      applyToEnvironment(environment) {
        return nonShareablePlugin({ outputName: environment.name })
      },
    },
  ],
})

Vite 會匯出 perEnvironmentPlugin 協助程式,以簡化這些不需要其他鉤子的情況

js
import { nonShareablePlugin } from 'non-shareable-plugin'

export default defineConfig({
  plugins: [
    perEnvironmentPlugin('per-environment-plugin', (environment) =>
      nonShareablePlugin({ outputName: environment.name }),
    ),
  ],
})

建置鉤子中的環境

就像在開發期間一樣,外掛鉤子也會在建置期間接收環境實例,取代 ssr 布林值。這也適用於 renderChunkgenerateBundle 和其他僅限建置的鉤子。

建置期間共用的外掛

在 Vite 6 之前,外掛管道在開發期間和建置期間以不同的方式運作

  • 在開發期間:外掛會共用
  • 在建置期間:外掛會針對每個環境隔離(在不同的程序中:vite build,然後是 vite build --ssr)。

這迫使框架在 client 建置和 ssr 建置之間,透過寫入檔案系統的資訊清單檔案來共用狀態。在 Vite 6 中,我們現在在單一程序中建置所有環境,因此外掛管道和環境間通訊的方式可以與開發對齊。

在未來的主要版本(Vite 7 或 8)中,我們的目標是完全對齊

在建置期間也會共用單一的 ResolvedConfig 實例,允許在整個應用程式建置程序層級進行快取,就像我們在開發期間使用 WeakMap<ResolvedConfig, CachedData> 所做的一樣。

對於 Vite 6,我們需要採取較小的步驟來保持回溯相容性。生態系統外掛目前使用 config.build 而不是 environment.config.build 來存取設定,因此我們需要預設針對每個環境建立新的 ResolvedConfig。專案可以選擇加入共用完整的設定和外掛管道,將 builder.sharedConfigBuild 設定為 true

此選項起初只會適用於一小部分的專案,因此外掛作者可以選擇加入,以透過將 sharedDuringBuild 旗標設定為 true 來共用特定外掛。這允許輕鬆地共用一般外掛的狀態

js
function myPlugin() {
  // Share state among all environments in dev and build
  const sharedState = ...
  return {
    name: 'shared-plugin',
    transform(code, id) { ... },

    // Opt-in into a single instance for all environments
    sharedDuringBuild: true,
  }
}

在 MIT 授權下發布。(ccee3d7c)