Совместимость с браузерами через @vitejs/plugin-legacy
### Назначение `@vitejs/plugin-legacy`
Современная экосистема Vite ориентирована на браузеры с поддержкой ES-модулей, `import`, `dynamic import`, `async/await`, `Promise`, `fetch` и других современных возможностей JavaScript. По умолчанию сборка Vite создаёт оптимизированный современный бандл, который отлично работает в актуальных версиях Chrome, Firefox, Edge и Safari.
Проблема возникает при необходимости поддержки старых браузеров:
* Internet Explorer 11
* старые версии Safari
* устаревшие Chromium-браузеры
* Android Browser
* старые WebView
* корпоративные браузеры с ограниченными обновлениями
Для обеспечения совместимости используется плагин `@vitejs/plugin-legacy`.
Основные задачи плагина:
* генерация legacy-бандлов;
* автоматическая транспиляция современного JavaScript;
* добавление полифилов;
* поддержка старых браузеров без ручной настройки Babel;
* создание dual-build архитектуры.
---
## Установка плагина
Установка выполняется через npm:
```bash
npm install @vitejs/plugin-legacy --save-dev
```
Либо через yarn:
```bash
yarn add @vitejs/plugin-legacy -D
```
Либо через pnpm:
```bash
pnpm add @vitejs/plugin-legacy -D
```
---
## Базовое подключение
Файл `vite.config.js`:
```js
import { defineConfig } from 'vite'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
legacy()
]
})
```
После подключения Vite начинает создавать:
* современный бандл;
* legacy-бандл;
* специальные загрузчики для определения поддержки ES-модулей.
---
## Как работает plugin-legacy
Плагин создаёт два набора файлов:
### Современная сборка
Используется браузерами с поддержкой:
* ``
* ES Modules
* современного JavaScript
Пример:
```html
```
---
### Legacy-сборка
Используется старыми браузерами:
```html
```
Атрибут `nomodule` сообщает браузеру:
* современные браузеры игнорируют этот файл;
* старые браузеры загружают legacy-версию.
---
## Принцип dual-build
Vite одновременно создаёт:
| Тип сборки | Назначение |
| ------------ | -------------------- |
| Modern Build | Современные браузеры |
| Legacy Build | Старые браузеры |
Это позволяет:
* не замедлять современные браузеры;
* сохранить поддержку устаревших систем;
* уменьшить размер modern-бандла;
* избежать полной транспиляции проекта.
---
## Настройка targets
Главная настройка плагина — `targets`.
Пример:
```js
legacy({
targets: ['defaults', 'not IE 11']
})
```
Плагин использует Browserslist-синтаксис.
---
## Популярные targets
### Поддержка IE11
```js
legacy({
targets: ['IE 11']
})
```
---
### Поддержка последних браузеров
```js
legacy({
targets: ['last 2 versions']
})
```
---
### Мобильные браузеры
```js
legacy({
targets: [
'Android >= 8',
'iOS >= 12'
]
})
```
---
### Корпоративная совместимость
```js
legacy({
targets: [
'chrome 64',
'edge 79',
'firefox 67'
]
})
```
---
## Использование Browserslist
Плагин поддерживает отдельный файл `.browserslistrc`.
Пример:
```txt
last 2 versions
not dead
> 0.5%
IE 11
```
Тогда конфигурация становится проще:
```js
legacy()
```
Плагин автоматически использует Browserslist.
---
## Генерация полифилов
Одной транспиляции недостаточно.
Старые браузеры могут не поддерживать:
* `Promise`
* `fetch`
* `Array.from`
* `Object.assign`
* `Symbol`
* `URL`
* `Map`
* `Set`
Плагин автоматически подключает необходимые полифилы.
---
## Автоматическое определение полифилов
По умолчанию plugin-legacy анализирует код проекта и добавляет только нужные полифилы.
Пример:
```js
legacy({
targets: ['defaults']
})
```
Это уменьшает размер legacy-бандла.
---
## Ручное указание полифилов
Иногда требуется принудительное добавление полифилов.
Пример:
```js
legacy({
polyfills: [
'es.promise',
'es.array.iterator',
'es.object.assign'
]
})
```
---
## Использование core-js
Plugin-legacy использует `core-js`.
При необходимости можно установить конкретную версию:
```bash
npm install core-js
```
---
## Отключение полифилов
Если проект уже использует собственные полифилы:
```js
legacy({
polyfills: false
})
```
---
## Дополнительные modern polyfills
Иногда даже современные браузеры требуют некоторые полифилы.
Пример:
```js
legacy({
modernPolyfills: true
})
```
---
## Настройка modernPolyfills
Можно указать конкретные возможности:
```js
legacy({
modernPolyfills: [
'es.promise.finally'
]
})
```
---
## Render Legacy Chunks
Параметр `renderLegacyChunks` отвечает за создание legacy-файлов.
Пример:
```js
legacy({
renderLegacyChunks: true
})
```
---
### Полное отключение legacy-бандла
```js
legacy({
renderLegacyChunks: false
})
```
В этом случае останутся только полифилы.
---
## Настройка additionalLegacyPolyfills
Позволяет подключать собственные полифилы.
Пример:
```js
legacy({
additionalLegacyPolyfills: [
'regenerator-runtime/runtime'
]
})
```
---
## Транспиляция async/await
Старые браузеры не поддерживают `async/await`.
Исходный код:
```js
async function loadData() {
const response = await fetch('/api/data')
return response.json()
}
```
После транспиляции:
* генерируется код через generators;
* подключается `regenerator-runtime`;
* добавляются вспомогательные функции Babel.
---
## Поддержка dynamic import
Современный код:
```js
const module = await import('./module.js')
```
В legacy-сборке:
* создаются fallback-механизмы;
* генерируется совместимый загрузчик;
* выполняется транспиляция динамического импорта.
---
## SystemJS в plugin-legacy
Для работы legacy-модулей Vite использует SystemJS.
SystemJS:
* эмулирует модульную систему;
* позволяет использовать модули в старых браузерах;
* загружает legacy-чанки.
---
## Генерация legacy-файлов
После сборки в папке `dist` появляются файлы:
```txt
assets/
index.js
vendor.js
index-legacy.js
vendor-legacy.js
```
---
## HTML после сборки
Plugin-legacy автоматически модифицирует HTML.
Пример:
```html
```
---
## Использование с TypeScript
Plugin-legacy полностью совместим с TypeScript.
Пример:
```ts
import { defineConfig } from 'vite'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
legacy({
targets: ['defaults']
})
]
})
```
---
## Использование с React
Конфигурация:
```js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
react(),
legacy({
targets: ['defaults']
})
]
})
})
```
---
## Использование с Vue
```js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
vue(),
legacy()
]
})
```
---
## Использование с Svelte
```js
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
svelte(),
legacy()
]
})
```
---
## Проблемы размера сборки
Legacy-бандлы значительно увеличивают:
* размер JavaScript;
* время сборки;
* объём полифилов;
* количество чанков.
Особенно заметно при поддержке IE11.
---
## Причины увеличения размера
### Транспиляция ES6+
Современный код:
```js
const sum = (a, b) => a + b
```
Legacy-версия:
```js
var sum = function(a, b) {
return a + b
}
```
---
### Async/Await
`async/await` превращается в объёмный generator-код.
---
### Полифилы
Подключение `core-js` может добавить десятки килобайт.
---
## Оптимизация legacy-сборки
### Ограничение targets
Плохой вариант:
```js
targets: ['IE 11']
```
Лучший вариант:
```js
targets: [
'chrome >= 80',
'safari >= 13'
]
```
---
### Удаление ненужных полифилов
```js
legacy({
polyfills: [
'es.promise'
]
})
```
---
### Анализ реальной аудитории
Поддержка IE11 имеет смысл только при наличии пользователей этого браузера.
---
## Особенности поддержки Safari
Старые версии Safari имеют частичную поддержку ES-модулей.
Проблемы возникают с:
* `dynamic import`;
* `import.meta`;
* async-модулями;
* preload.
Plugin-legacy автоматически обрабатывает многие из этих случаев.
---
## CSP и plugin-legacy
При строгой Content Security Policy могут возникать ошибки:
```txt
Refused to execute inline script
```
Причина — встроенные runtime-скрипты.
Решение:
```html
Content-Security-Policy:
script-src 'self'
```
Иногда требуется использование nonce или hash.
---
## Особенности preload
Современные браузеры используют:
```html
```
Старые браузеры его не поддерживают.
Plugin-legacy создаёт fallback-механизмы.
---
## Проверка legacy-сборки
Проверить поддержку можно через:
* BrowserStack;
* Sauce Labs;
* старые устройства;
* эмуляторы браузеров.
---
## Проверка через DevTools
В Chrome DevTools можно:
1. Открыть Network.
2. Проверить загрузку:
* `index.js`
* `index-legacy.js`
3. Убедиться в корректной выдаче бандлов.
---
## Типичные ошибки
### Legacy build отсутствует
Причина:
```js
renderLegacyChunks: false
```
---
### Полифилы не подключаются
Причины:
* неверные targets;
* отключён polyfills;
* отсутствует core-js.
---
### Ошибки Symbol
Пример:
```txt
Symbol is undefined
```
Решение:
```js
legacy({
polyfills: ['es.symbol']
})
```
---
### Ошибки Promise
```txt
Promise is undefined
```
Решение:
```js
legacy({
polyfills: ['es.promise']
})
```
---
## Поддержка import.meta
Старые браузеры не поддерживают:
```js
import.meta.env
```
Plugin-legacy заменяет такие конструкции на совместимые значения во время сборки.
---
## Совместимость с SSR
При использовании SSR следует учитывать:
* legacy-сборка применяется только к клиентскому коду;
* серверная часть обычно работает в Node.js;
* plugin-legacy не предназначен для Node runtime.
---
## Работа с CDN
При использовании CDN важно учитывать:
* кэширование modern и legacy-файлов;
* корректные заголовки;
* отдельные чанки для старых браузеров.
---
## Стратегия progressive enhancement
Plugin-legacy реализует progressive enhancement:
* современные браузеры получают быстрый современный код;
* старые браузеры получают совместимый код;
* функциональность сохраняется для всех пользователей.
---
## Производительность modern-браузеров
Без plugin-legacy весь проект пришлось бы транспилировать до ES5.
Это привело бы к:
* увеличению размера JS;
* ухудшению tree-shaking;
* снижению производительности.
Dual-build архитектура Vite позволяет избежать этих проблем.
---
## Когда plugin-legacy действительно нужен
Плагин необходим:
* в корпоративных системах;
* при поддержке старых устройств;
* для государственных порталов;
* для embedded WebView;
* в B2B-приложениях;
* в проектах с длительным жизненным циклом.
---
## Когда plugin-legacy не нужен
Во многих современных проектах поддержка старых браузеров избыточна:
* внутренние SPA;
* современные мобильные приложения;
* новые SaaS-сервисы;
* проекты только для Chromium;
* приложения Electron.
Отказ от legacy-поддержки позволяет:
* ускорить сборку;
* уменьшить размер бандлов;
* сократить количество полифилов;
* упростить инфраструктуру.