TYPESCRIPT
#vite#webpack#migration#env#alias#define

Миграция Webpack → Vite: типовые отличия (process.env, алиасы, define)

Практические пары «было (Webpack) / стало (Vite)»: process.env.NODE_ENV → import.meta.env, алиасы, DefinePlugin → define. Только документированный API, минимальный показательный код и выводы по миграции.

Как использовать

  1. Использовать как чеклист при переносе проекта с Webpack на Vite
  2. Заменить в коде и конфиге по блокам ниже; проверить по списку «что проверить в первую очередь»

Ниже — только реальные, документированные отличия (Vite Env Variables, Vite Config, Webpack DefinePlugin).


1) process.env.NODE_ENVimport.meta.env

Webpack (было)

// В коде приложения
if (process.env.NODE_ENV === 'production') {
  // только в проде
}
const apiBase = process.env.REACT_APP_API_URL ?? '';

Почему в Webpack это работало: Webpack при сборке подставляет строки через DefinePlugin (или через mode: 'production'). В бандле нет реального process.env — только подставленные значения. Переменные из .env часто подхватываются через dotenv + DefinePlugin или через CRA/другие шаблоны.

Vite (стало)

// В коде приложения
if (import.meta.env.PROD) {
  // только в проде
}
// Кастомные переменные — только с префиксом VITE_, иначе не попадут в клиентский бандл
const apiBase = import.meta.env.VITE_API_URL ?? '';

Почему в Vite иначе: Vite не подменяет process.env в браузерном коде. Используется стандартный import.meta.env: в нём есть MODE, DEV, PROD, SSR, а переменные из .env доступны только если имя начинается с VITE_ (защита от случайной утечки серверных секретов в клиент).


2) Алиасы путей

Webpack (было)

// webpack.config.js
const path = require('path');

module.exports = {
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components'),
    },
  },
};
// В коде: import from '@components/Button'
import { Button } from '@components/Button';

Почему в Webpack это работало: В Node-конфиге есть __dirname; Webpack резолвит алиасы на этапе сборки и подставляет реальные пути в граф модулей.

Vite (стало)

// vite.config.ts
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vite';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components'),
    },
  },
});
// В коде — без изменений: import from '@components/Button'
import { Button } from '@components/Button';

Почему в Vite иначе: Конфиг Vite — ESM-модуль, в нём нет __dirname. Нужно получить путь к директории конфига через path.dirname(fileURLToPath(import.meta.url)). Синтаксис алиасов в коде тот же.


3) Глобальные константы (DefinePlugin → define)

Webpack (было)

// webpack.config.js
const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production'),
      __APP_VERSION__: JSON.stringify(process.env.npm_package_version || '0.0.0'),
      __BUILD_TIME__: JSON.stringify(new Date().toISOString()),
    }),
  ],
};
// В коде
console.log(__APP_VERSION__, __BUILD_TIME__);

Почему в Webpack это работало: DefinePlugin заменяет идентификаторы на строки (или выражения) на этапе сборки. В бандле не остаётся ссылок на __APP_VERSION__ — только подставленное значение.

Vite (стало)

// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  define: {
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version || '0.0.0'),
    __BUILD_TIME__: JSON.stringify(new Date().toISOString()),
  },
});
// В коде — без изменений
console.log(__APP_VERSION__, __BUILD_TIME__);

Почему в Vite иначе: В Vite нет DefinePlugin; ту же роль играет опция define в корне конфига. Значения тоже передаются как строки (через JSON.stringify), иначе подставится идентификатор, а не строка. process.env.NODE_ENV в Vite не задаётся через define — для режима сборки используются import.meta.env.PROD / import.meta.env.DEV.


Вывод: что чаще всего ломается при миграции

  1. process.env в клиентском коде — код падает с «process is not defined» или переменные пустые. Заменить на import.meta.env и префикс VITE_ для своих переменных из .env.
  2. Алиасы в конфиге — в Vite конфиге нет __dirname; без fileURLToPath(import.meta.url) путь к src задать нельзя. Импорты в коде менять не нужно.
  3. Глобальные константы — везде, где использовались DefinePlugin/ProvidePlugin, перенести в define в vite.config и оставить в коде те же имена.
  4. SVG / raw / file-loader — в Vite нет лоадеров; SVG как компонент или raw-строка делаются через плагины (например vite-plugin-svgr) или через ?raw/?url суффиксы.
  5. Публичный путь и base — если приложение не в корне домена, в Vite задать base: '/app/'; в Webpack часто было output.publicPath.

Что проверить в первую очередь

  1. Поиск по проекту: process.env → заменить на import.meta.env (и переименовать переменные в .env в VITE_* где нужно).
  2. Конфиг: алиасы через __dirname из fileURLToPath(import.meta.url); глобалы из DefinePlugin — в define.
  3. Типы (TypeScript): добавить в env.d.ts объявление interface ImportMetaEnv { VITE_*: string } и при необходимости define-глобалов, чтобы не было ошибок типов.
  4. Сборка: запустить vite build, проверить отсутствие «process is not defined» и корректность подставленных констант в бандле.
  5. Dev: запустить vite, убедиться, что алиасы и переменные окружения работают так же, как в проде.

После этого по необходимости разбирать лоадеры (SVG, CSS-модули, и т.д.) и при необходимости — base и publicPath.