Условное применение плагина по apply

Свойство apply используется для условного подключения плагина в зависимости от режима работы Vite. Оно позволяет запускать плагин только:

  • во время разработки;
  • только при production-сборке;
  • только для SSR;
  • только для client-сборки;
  • при определённых командах (serve или build);
  • при произвольных пользовательских условиях.

Без apply плагин участвует во всех этапах жизненного цикла Vite, включая dev-сервер и production-build. Во многих случаях это приводит к лишней обработке файлов, ненужным трансформациям и ухудшению производительности.


Базовый синтаксис

apply поддерживает два варианта:

  1. Строковое значение:

    • 'serve'
    • 'build'
  2. Функцию:

    • получает контекст запуска;
    • возвращает true или false.

Простейший пример:

export default function myPlugin() {
  return {
    name: 'my-plugin',
    apply: 'serve',
  }
}

Такой плагин будет активен только при запуске dev-сервера.


Значение serve

Режим serve соответствует команде:

vite

или:

vite serve

Плагин подключается только во время разработки.

Пример:

export default function devLoggerPlugin() {
  return {
    name: 'dev-logger',

    apply: 'serve',

    transform(code, id) {
      console.log('Transform:', id)

      return code
    },
  }
}

Во время production-сборки плагин полностью игнорируется.


Значение build

Режим build соответствует команде:

vite build

Плагин выполняется исключительно при production-сборке.

Пример:

export default function bannerPlugin() {
  return {
    name: 'banner-plugin',

    apply: 'build',

    generateBundle(_, bundle) {
      for (const file of Object.values(bundle)) {
        if (file.type === 'chunk') {
          file.code =
            '/* Production Build */\n' + file.code
        }
      }
    },
  }
}

Такой подход особенно полезен для:

  • минификации;
  • оптимизации;
  • генерации manifest-файлов;
  • анализа bundle;
  • постобработки output-файлов.

Внутренний принцип работы

Во время инициализации Vite анализирует каждый плагин.

Если присутствует apply, Vite проверяет:

  • текущую команду;
  • тип сборки;
  • SSR-режим;
  • пользовательские условия.

Если условие не выполняется:

  • плагин не подключается;
  • хуки не регистрируются;
  • код плагина не участвует в пайплайне.

Это важное отличие от ручных проверок внутри самих хуков.

Плохо:

transform(code) {
  if (process.env.NODE_ENV !== 'development') {
    return null
  }

  return code
}

Правильно:

apply: 'serve'

Во втором случае Vite вообще не активирует плагин.


Использование функции в apply

Функция предоставляет полный контроль над условиями подключения.

Сигнатура:

apply(config, env)

Где:

  • config — итоговая конфигурация Vite;
  • env — информация о текущем запуске.

Структура объекта env

Объект env содержит:

{
  command,
  mode,
  ssrBuild
}

command

Текущая команда:

'serve'
'build'

mode

Текущий режим:

vite --mode development
vite build --mode production
vite build --mode staging

ssrBuild

Показывает SSR-сборку:

true
false

Условие по command

Пример:

export default function plugin() {
  return {
    name: 'conditional-plugin',

    apply(_, { command }) {
      return command === 'build'
    },
  }
}

Аналог:

apply: 'build'

Но функциональная форма позволяет строить сложные условия.


Условие по mode

Подключение плагина только в staging-режиме:

export default function stagingPlugin() {
  return {
    name: 'staging-plugin',

    apply(_, { mode }) {
      return mode === 'staging'
    },
  }
}

Запуск:

vite build --mode staging

Условие по SSR

SSR-сборка:

export default function ssrPlugin() {
  return {
    name: 'ssr-plugin',

    apply(_, { ssrBuild }) {
      return ssrBuild
    },
  }
}

Только client-build:

export default function clientPlugin() {
  return {
    name: 'client-plugin',

    apply(_, { ssrBuild }) {
      return !ssrBuild
    },
  }
}

Комбинированные условия

Часто требуется учитывать сразу несколько факторов.

Пример:

export default function productionClientPlugin() {
  return {
    name: 'production-client-plugin',

    apply(_, { command, ssrBuild }) {
      return command === 'build' && !ssrBuild
    },
  }
}

Плагин работает:

  • только при production-build;
  • только для браузерной сборки;
  • не участвует в SSR.

Проверка пользовательских переменных окружения

apply удобно комбинируется с .env.

Пример:

VITE_ENABLE_ANALYZER=true
export default function analyzerPlugin() {
  return {
    name: 'analyzer-plugin',

    apply(_, env) {
      return (
        env.command === 'build' &&
        process.env.VITE_ENABLE_ANALYZER === 'true'
      )
    },
  }
}

Использование с defineConfig

Очень распространённый подход — условное добавление плагинов прямо в массив plugins.

Пример:

import { defineConfig } from 'vite'
import inspect from 'vite-plugin-inspect'

export default defineConfig(({ command }) => {
  return {
    plugins: [
      command === 'serve' && inspect(),
    ],
  }
})

Однако здесь существует важное отличие.


Разница между apply и условным добавлением в plugins

Условное добавление

plugins: [
  command === 'serve' && plugin(),
]

Плагин вообще не создаётся.

apply

plugins: [
  plugin(),
]
apply: 'serve'

Плагин создаётся, но Vite решает, активировать его или нет.


Когда лучше использовать apply

apply особенно полезен, если:

  • требуется единый экземпляр плагина;
  • логика включения относится к самому плагину;
  • плагин публикуется как npm-пакет;
  • нужно скрыть внутренние условия от пользователя;
  • требуется декларативность.

Когда лучше условное добавление

Условное добавление лучше подходит, если:

  • создание плагина дорогостоящее;
  • требуется динамический import;
  • нужно полностью исключить зависимость;
  • плагин нужен только в конкретной конфигурации.

Пример:

plugins: [
  process.env.ANALYZE === 'true'
    ? visualizer()
    : null,
]

Комбинирование с enforce

apply и enforce работают независимо.

Пример:

export default function plugin() {
  return {
    name: 'pre-dev-plugin',

    apply: 'serve',

    enforce: 'pre',
  }
}

Плагин:

  • запускается только в dev;
  • выполняется до обычных плагинов.

Пример dev-only HMR-плагина

export default function hmrDebugPlugin() {
  return {
    name: 'hmr-debug',

    apply: 'serve',

    handleHotUpdate(ctx) {
      console.log('Updated:', ctx.file)
    },
  }
}

Во время production-build HMR-хук вообще не существует.


Пример production-only оптимизации

export default function removeConsolePlugin() {
  return {
    name: 'remove-console',

    apply: 'build',

    transform(code, id) {
      if (!id.endsWith('.js')) {
        return null
      }

      return code.replace(/console\.log\(.*?\);?/g, '')
    },
  }
}

Пример SSR-only трансформации

export default function ssrGlobalsPlugin() {
  return {
    name: 'ssr-globals',

    apply(_, { ssrBuild }) {
      return ssrBuild
    },

    transform(code) {
      return code.replace(
        '__SERVER__',
        'true'
      )
    },
  }
}

Использование внутри factory-функций

Плагин может принимать параметры.

export default function myPlugin(options = {}) {
  return {
    name: 'my-plugin',

    apply(_, env) {
      if (options.devOnly) {
        return env.command === 'serve'
      }

      return true
    },
  }
}

Использование:

myPlugin({
  devOnly: true,
})

Частая ошибка: путаница между mode и NODE_ENV

В Vite mode и NODE_ENV — разные сущности.

Ошибка:

apply(_, env) {
  return env.mode === 'production'
}

Если запуск:

vite build --mode staging

то условие не выполнится.

Для production-build правильнее использовать:

env.command === 'build'

Частая ошибка: использование apply внутри хука

Неправильно:

transform(code) {
  if (this.apply === 'build') {
    return code
  }
}

apply — не runtime-параметр хука, а механизм подключения плагина.


Частая ошибка: возврат строки вместо boolean

Неправильно:

apply(_, env) {
  return env.command
}

Хотя JavaScript интерпретирует строку как truthy-значение, код становится неявным.

Правильно:

apply(_, env) {
  return env.command === 'serve'
}

Влияние на производительность

Правильное использование apply:

  • уменьшает количество зарегистрированных хуков;
  • сокращает время старта dev-сервера;
  • снижает объём работы Rollup;
  • уменьшает количество трансформаций;
  • сокращает потребление памяти.

Особенно заметен эффект:

  • в больших monorepo;
  • при десятках плагинов;
  • в SSR-приложениях;
  • при сложной цепочке трансформаций.

Архитектурный подход

Крупные проекты часто разделяют плагины по категориям:

Dev-only

apply: 'serve'

Примеры:

  • HMR-инструменты;
  • debug-плагины;
  • overlay;
  • inspector;
  • mock API.

Build-only

apply: 'build'

Примеры:

  • минификация;
  • анализ bundle;
  • генерация отчётов;
  • оптимизация assets.

SSR-only

apply(_, env) {
  return env.ssrBuild
}

Примеры:

  • node-specific polyfills;
  • серверные трансформации;
  • инъекция backend-констант.

Client-only

apply(_, env) {
  return !env.ssrBuild
}

Примеры:

  • browser API;
  • DOM-оптимизации;
  • клиентские runtime-injection.

Практический пример сложного условия

export default function analyticsPlugin() {
  return {
    name: 'analytics-plugin',

    apply(config, env) {
      return (
        env.command === 'build' &&
        !env.ssrBuild &&
        env.mode === 'production' &&
        config.build.minify !== false
      )
    },
  }
}

Условия:

  • production-build;
  • не SSR;
  • production-mode;
  • включена минификация.

Проверка активного плагина

Во время отладки полезно выводить информацию:

export default function debugPlugin() {
  return {
    name: 'debug-plugin',

    apply(config, env) {
      console.log(env)

      return true
    },
  }
}

Пример вывода:

{
  command: 'serve',
  mode: 'development',
  ssrBuild: false
}

Лучшие практики

Использование коротких условий

Хорошо:

apply: 'build'

Плохо:

apply(_, env) {
  return env.command === 'build'
}

Если достаточно строкового режима, функциональная форма не нужна.


Изоляция dev-инструментов

Все инструменты разработки желательно ограничивать:

apply: 'serve'

Это предотвращает случайное попадание debug-кода в production.


Изоляция SSR-логики

SSR-плагины не должны попадать в client-build.

Правильно:

apply(_, env) {
  return env.ssrBuild
}

Не смешивать разные режимы внутри хуков

Плохо:

transform(code, id) {
  if (isDev) {
    ...
  }

  if (isBuild) {
    ...
  }
}

Лучше разделить логику на несколько плагинов с разным apply.


Разделение одного плагина на несколько

Вместо:

export default function plugin() {
  return {
    name: 'mega-plugin',

    transform(code) {
      if (isDev) {
        ...
      }

      if (isBuild) {
        ...
      }
    },
  }
}

Предпочтительнее:

devPlugin()
buildPlugin()

С разными условиями:

apply: 'serve'

и

apply: 'build'

Такой подход:

  • упрощает поддержку;
  • улучшает читаемость;
  • уменьшает связность;
  • делает пайплайн Vite предсказуемее.