跳轉到內容

外掛環境 API

候選釋出版本 (Release Candidate)

環境 API 目前處於候選釋出階段。我們將在主要版本之間保持 API 的穩定性,以便生態系統能夠進行實驗和構建。但請注意,某些特定 API 仍被視為實驗性的。

一旦下游專案有時間試驗這些新功能並完成驗證,我們計劃在未來的主要版本中穩定這些新 API(可能會有破壞性變更)。

資源

請與我們分享您的反饋。

在鉤子(Hooks)中訪問當前環境

鑑於在 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 支援使用了一個額外的環境,以便擁有一個帶有 react-server 條件的獨立模組圖。

ts
  config(config: UserConfig) {
    return {
      environments: {
        rsc: {
          resolve: {
            conditions: ['react-server', ...defaultServerConditions],
          },
        },
      },
    }
  }

使用來自根級別環境配置的預設值,只需一個空物件即可註冊環境。

使用鉤子配置環境

config 鉤子執行時,環境的完整列表尚不清楚,且環境既可以受到根級別環境配置預設值的影響,也可以透過 config.environments 記錄顯式配置。外掛應使用 config 鉤子設定預設值。要配置每個環境,它們可以使用新的 configEnvironment 鉤子。該鉤子針對每個環境呼叫,並帶有其部分解析後的配置,包括最終預設值的解析。

ts
  configEnvironment(name: string, options: EnvironmentOptions) {
    // add "workerd" condition to the rsc environment
    if (name === 'rsc') {
      return {
        resolve: {
          conditions: ['workerd'],
        },
      }
    }
  }

hotUpdate 鉤子

  • 型別: (this: { environment: DevEnvironment }, options: HotUpdateOptions) => Array<EnvironmentModuleNode> | void | Promise<Array<EnvironmentModuleNode> | void>
  • 型別: async, sequential
  • 另見: HMR API

hotUpdate 鉤子允許外掛為給定環境執行自定義 HMR 更新處理。當檔案更改時,HMR 演算法會按照 server.environments 中的順序依次為每個環境執行,因此 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
      })
    }

外掛中的每環境狀態

鑑於同一個外掛例項用於不同的環境,外掛狀態需要以 this.environment 為鍵。這與生態系統已經使用的模式相同,即使用 ssr 布林值作為鍵來保持模組狀態,以避免混合客戶端和 SSR 模組狀態。可以使用 Map<Environment, State> 來分別為每個環境保留狀態。請注意,為了向後相容,除非設定了 perEnvironmentStartEndDuringDev: true 標誌,否則 buildStartbuildEnd 僅針對客戶端環境呼叫。對於 watchChangeperEnvironmentWatchChangeDuringDev: true 標誌也是如此。

js
function PerEnvironmentCountTransformedModulesPlugin() {
  const state = new Map<Environment, { count: number }>()
  return {
    name: 'count-transformed-modules',
    perEnvironmentStartEndDuringDev: true,
    buildStart() {
      state.set(this.environment, { count: 0 })
    },
    transform(id) {
      state.get(this.environment).count++
    },
    buildEnd() {
      console.log(this.environment.name, state.get(this.environment).count)
    }
  }
}

每環境外掛

外掛可以使用 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 }),
    ),
  ],
})

applyToEnvironment 鉤子在配置階段呼叫,目前由於生態系統中的專案會修改外掛,因此是在 configResolved 之後呼叫。未來環境外掛的解析可能會移至 configResolved 之前。

應用與外掛通訊

environment.hot 允許外掛與給定環境的應用程式端程式碼進行通訊。這等同於 客戶端-伺服器通訊功能,但支援除客戶端環境以外的其他環境。

注意

請注意,此功能僅適用於支援 HMR 的環境。

管理應用例項

請注意,在同一環境中可能同時執行多個應用程式例項。例如,如果您在瀏覽器中打開了多個標籤頁,每個標籤頁都是一個單獨的應用程式例項,並與伺服器有單獨的連線。

當建立新連線時,會在環境的 hot 例項上發出 vite:client:connect 事件。當連線關閉時,會發出 vite:client:disconnect 事件。

每個事件處理程式都會接收 NormalizedHotChannelClient 作為第二個引數。該客戶端是一個包含 send 方法的物件,可用於向特定的應用程式例項傳送訊息。客戶端引用對於同一連線始終相同,因此您可以保留它以跟蹤連線。

示例用法

外掛端

js
configureServer(server) {
  server.environments.ssr.hot.on('my:greetings', (data, client) => {
    // do something with the data,
    // and optionally send a response to that application instance
    client.send('my:foo:reply', `Hello from server! You said: ${data}`)
  })

  // broadcast a message to all application instances
  server.environments.ssr.hot.send('my:foo', 'Hello from server!')
}

應用端與客戶端-伺服器通訊功能相同。您可以使用 import.meta.hot 物件向外掛傳送訊息。

構建鉤子中的環境

與開發期間一樣,外掛鉤子在構建期間也會接收環境例項,從而替代了 ssr 布林值。這也適用於 renderChunkgenerateBundle 和其他僅在構建時呼叫的鉤子。

構建期間共享外掛

在 Vite 6 之前,外掛管道在開發和構建期間的工作方式不同:

  • 開發期間:外掛是共享的。
  • 構建期間:外掛為每個環境隔離(在不同的程序中:先 vite buildvite build --ssr)。

這迫使框架透過寫入檔案系統的 manifest 檔案在 client 構建和 ssr 構建之間共享狀態。在 Vite 6 中,我們現在在一個程序中構建所有環境,因此外掛管道和環境間通訊的方式可以與開發環境保持一致。

在未來的大版本中,我們可能會實現完全一致:

構建期間還將共享一個 ResolvedConfig 例項,從而允許在整個應用構建流程級別進行快取,方式與我們在開發期間使用 WeakMap<ResolvedConfig, CachedData> 的方式相同。

對於 Vite 6,我們需要採取較小的步驟來保持向後相容性。生態系統外掛目前使用 config.build 而不是 environment.config.build 來訪問配置,因此我們預設需要為每個環境建立一個新的 ResolvedConfig。專案可以透過設定 builder.sharedConfigBuildtrue 來選擇共享完整配置和外掛管道。

該選項起初僅適用於一小部分專案,因此外掛作者可以設定 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,
  }
}