Совместимость с браузерами через @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-поддержки позволяет: * ускорить сборку; * уменьшить размер бандлов; * сократить количество полифилов; * упростить инфраструктуру.