跳至内容

插件的环境 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 更新处理。当文件发生更改时,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
      })
    }

每个环境的插件

插件可以使用 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)