外掛環境 API
候選釋出版本 (Release Candidate)
環境 API 目前處於候選釋出階段。我們將在主要版本之間保持 API 的穩定性,以便生態系統能夠進行實驗和構建。但請注意,某些特定 API 仍被視為實驗性的。
一旦下游專案有時間試驗這些新功能並完成驗證,我們計劃在未來的主要版本中穩定這些新 API(可能會有破壞性變更)。
資源
請與我們分享您的反饋。
在鉤子(Hooks)中訪問當前環境
鑑於在 Vite 6 之前只有兩個環境(client 和 ssr),一個 ssr 布林值足以識別 Vite API 中的當前環境。外掛鉤子在最後一個選項引數中接收一個 ssr 布林值,並且許多 API 期望一個可選的最後一個 ssr 引數來將模組正確關聯到對應的環境(例如 server.moduleGraph.getModuleByUrl(url, { ssr }))。
隨著可配置環境的出現,我們現在有了一種統一的方式來在外掛中訪問其選項和例項。外掛鉤子現在在上下文中暴露了 this.environment,而之前期望 ssr 布林值的 API 現在被限制在適當的環境中(例如 environment.moduleGraph.getModuleByUrl(url))。
Vite 伺服器具有共享的外掛管道,但處理模組時始終在特定環境的上下文中進行。environment 例項可在外掛上下文中獲取。
外掛可以使用 environment 例項根據該環境的配置(可透過 environment.config 訪問)來更改處理模組的方式。
transform(code, id) {
console.log(this.environment.config.resolve.conditions)
}使用鉤子註冊新環境
外掛可以在 config 鉤子中新增新環境。例如,RSC 支援使用了一個額外的環境,以便擁有一個帶有 react-server 條件的獨立模組圖。
config(config: UserConfig) {
return {
environments: {
rsc: {
resolve: {
conditions: ['react-server', ...defaultServerConditions],
},
},
},
}
}使用來自根級別環境配置的預設值,只需一個空物件即可註冊環境。
使用鉤子配置環境
當 config 鉤子執行時,環境的完整列表尚不清楚,且環境既可以受到根級別環境配置預設值的影響,也可以透過 config.environments 記錄顯式配置。外掛應使用 config 鉤子設定預設值。要配置每個環境,它們可以使用新的 configEnvironment 鉤子。該鉤子針對每個環境呼叫,並帶有其部分解析後的配置,包括最終預設值的解析。
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 鉤子會被多次呼叫。該鉤子接收一個帶有以下簽名的上下文物件:
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 更加準確。
返回一個空陣列並執行完全重新載入
jshotUpdate({ 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 處理
jshotUpdate() { if (this.environment.name !== 'client') return this.environment.hot.send({ type: 'custom', event: 'special-update', data: {} }) return [] }客戶端程式碼應使用 HMR API 註冊相應的處理程式(這可以由同一外掛的
transform鉤子注入)。jsif (import.meta.hot) { import.meta.hot.on('special-update', (data) => { // perform custom update }) }
外掛中的每環境狀態
鑑於同一個外掛例項用於不同的環境,外掛狀態需要以 this.environment 為鍵。這與生態系統已經使用的模式相同,即使用 ssr 布林值作為鍵來保持模組狀態,以避免混合客戶端和 SSR 模組狀態。可以使用 Map<Environment, State> 來分別為每個環境保留狀態。請注意,為了向後相容,除非設定了 perEnvironmentStartEndDuringDev: true 標誌,否則 buildStart 和 buildEnd 僅針對客戶端環境呼叫。對於 watchChange 和 perEnvironmentWatchChangeDuringDev: true 標誌也是如此。
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 函式定義它應該應用於哪些環境。
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 鉤子可以輕鬆地將其轉換為每環境外掛。
import { nonShareablePlugin } from 'non-shareable-plugin'
export default defineConfig({
plugins: [
{
name: 'per-environment-plugin',
applyToEnvironment(environment) {
return nonShareablePlugin({ outputName: environment.name })
},
},
],
})Vite 匯出了一個 perEnvironmentPlugin 助手來簡化那些不需要其他鉤子的情況。
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 方法的物件,可用於向特定的應用程式例項傳送訊息。客戶端引用對於同一連線始終相同,因此您可以保留它以跟蹤連線。
示例用法
外掛端
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 布林值。這也適用於 renderChunk、generateBundle 和其他僅在構建時呼叫的鉤子。
構建期間共享外掛
在 Vite 6 之前,外掛管道在開發和構建期間的工作方式不同:
- 開發期間:外掛是共享的。
- 構建期間:外掛為每個環境隔離(在不同的程序中:先
vite build再vite build --ssr)。
這迫使框架透過寫入檔案系統的 manifest 檔案在 client 構建和 ssr 構建之間共享狀態。在 Vite 6 中,我們現在在一個程序中構建所有環境,因此外掛管道和環境間通訊的方式可以與開發環境保持一致。
在未來的大版本中,我們可能會實現完全一致:
- 開發和構建期間:外掛是共享的,支援 每環境過濾。
構建期間還將共享一個 ResolvedConfig 例項,從而允許在整個應用構建流程級別進行快取,方式與我們在開發期間使用 WeakMap<ResolvedConfig, CachedData> 的方式相同。
對於 Vite 6,我們需要採取較小的步驟來保持向後相容性。生態系統外掛目前使用 config.build 而不是 environment.config.build 來訪問配置,因此我們預設需要為每個環境建立一個新的 ResolvedConfig。專案可以透過設定 builder.sharedConfigBuild 為 true 來選擇共享完整配置和外掛管道。
該選項起初僅適用於一小部分專案,因此外掛作者可以設定 sharedDuringBuild 標誌為 true,以選擇讓特定外掛實現共享。這使得常規外掛可以輕鬆共享狀態。
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,
}
}