Skip to content

Environment API для плагинов

Экспериментально

Environment API является экспериментальным. Мы будем поддерживать стабильность API в Vite 6, чтобы дать экосистеме возможность экспериментировать и строить на его основе. Мы планируем стабилизировать эти новые API с возможными изменениями, нарушающими обратную совместимость, в Vite 7.

Ресурсы:

Пожалуйста, поделитесь с нами своим мнением.

Доступ к текущему окружению в хуках

Учитывая, что до Vite 6 существовало только два окружения (client и ssr), булевый параметр ssr был достаточен для идентификации текущего окружения в API Vite. Хуки плагинов получали булевый параметр ssr в последнем параметре опций, и несколько API ожидали необязательный последний параметр ssr, чтобы правильно ассоциировать модули с соответствующим окружением (например, server.moduleGraph.getModuleByUrl(url, { ssr })).

С появлением настраиваемых окружений у нас теперь есть единый способ доступа к их параметрам и экземплярам в плагинах. Хуки плагинов теперь предоставляют this.environment в своем контексте, а API, которые ранее ожидали булевый параметр ssr, теперь ограничены соответствующим окружением (например, 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
    
      // Ручная инвалидация модулей
      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) => {
        // пользовательское обновление
      })
    }

Плагины для каждого окружения

Плагин может определить, к каким окружениям он должен применяться, с помощью функции applyToEnvironment.

js
const UnoCssPlugin = () => {
  // общее глобальное состояние
  return {
    buildStart() {
      // инициализация состояния для каждого окружения с WeakMap<Environment,Data>
      // с использованием this.environment
    },
    configureServer() {
      // используйте глобальные хуки как обычно
    },
    applyToEnvironment(environment) {
      // верните true, если этот плагин должен быть активен в этом окружении,
      // или верните новый плагин, чтобы заменить его.
      // если хук не используется, плагин активен во всех окружениях
    },
    resolveId(id, importer) {
      // вызывается только для окружений, к которым этот плагин применяется
    },
  }
}

Если плагин не учитывает окружение и имеет состояние, которое не связано с текущим окружением, хук 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. Это также работает для renderChunk, generateBundle и других хуков, которые используются только во время сборки.

Общие плагины во время сборки

До 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() {
  // Делимся состоянием между всеми окружениями как во время разработки, так и во время сборки
  const sharedState = ...
  return {
    name: 'shared-plugin',
    transform(code, id) { ... },

    // Выбор единственного экземпляра для всех окружений
    sharedDuringBuild: true,
  }
}

Выпущено под лицензией MIT. (dev)