Использование манифеста на сервере

## Сущность манифеста Vite и его роль в серверной интеграции При сборке проекта Vite в production-режиме формируется набор статических ассетов с хешированными именами файлов. Эти имена генерируются для решения задачи кеширования: каждый изменённый файл получает новый хеш, что позволяет браузеру обновлять ресурсы только при реальных изменениях. Ключевым артефактом сборки становится файл манифеста, обычно расположенный по пути `dist/manifest.json`. Он представляет собой JSON-структуру, которая связывает исходные модули приложения с итоговыми скомпилированными файлами. Структура манифеста позволяет серверной части приложения точно определить, какие JS, CSS и дополнительные ресурсы необходимо подключить для конкретной точки входа. --- ## Структура manifest.json Типичный `manifest.json` содержит объект, где ключами выступают исходные входные файлы, например `index.html` или `src/main.js`, а значениями — описание собранных ресурсов. Пример упрощённой структуры: ```json { "src/main.js": { "file": "assets/main.8f3a1c2d.js", "css": ["assets/main.4c9d2f1e.css"], "imports": ["_vendor.7a91c2.js"], "isEntry": true } } ``` Основные поля: * **file** — основной JS-бандл для точки входа * **css** — связанные стили, выделенные в отдельные файлы * **imports** — дополнительные чанки, которые нужно загрузить * **isEntry** — признак входной точки приложения * **assets** — дополнительные ресурсы (изображения, шрифты), если они связаны с модулем --- ## Зачем серверу нужен manifest В отличие от dev-режима, где Vite сам управляет модулями через ESM и middleware, production-сборка полностью статична. Сервер больше не участвует в сборке, а лишь отдаёт готовые файлы. Проблема заключается в том, что имена файлов динамические: ``` main.js → main.8f3a1c2d.js ``` Без манифеста сервер не может заранее знать итоговые пути к файлам. Поэтому manifest становится индексом соответствий между логическими точками входа и физическими ресурсами. --- ## Базовый сценарий использования на сервере Серверная логика обычно выполняет три шага: 1. Загружает `manifest.json` 2. Находит нужную entry-point запись 3. Генерирует HTML с правильными ссылками на ассеты ### Пример на Node.js ```js import fs from 'fs'; const manifest = JSON.parse( fs.readFileSync('./dist/manifest.json', 'utf-8') ); const entry = manifest['src/main.js']; const scripts = ` `; const styles = (entry.css || []) .map((href) => `
  • `) .join('\n'); const html = ` ${styles}
    ${scripts} `; ``` --- ## Подключение в SSR (Server-Side Rendering) При использовании SSR манифест становится критическим элементом гидратации клиентской части приложения. Сервер: * рендерит HTML * определяет необходимые entry chunks * подключает только нужные JS/CSS файлы ### Логика SSR интеграции ```js const manifest = JSON.parse(fs.readFileSync('./dist/manifest.json', 'utf-8')); function renderApp() { const entry = manifest['src/entry-client.js']; return ` ${(entry.css || []) .map((c) => `
  • `) .join('')}
    `; } ``` --- ## Работа с code splitting Vite автоматически разделяет код на чанки при динамических import: ```js const module = await import('./heavy-module.js'); ``` В manifest это отражается через поле `imports`. Пример: ```json { "src/main.js": { "file": "assets/main.js", "imports": ["assets/chunk-1.js", "assets/chunk-2.js"] } } ``` На сервере это означает необходимость учитывать зависимости, если рендер требует полного набора ресурсов. --- ## Рекурсивное разрешение зависимостей В сложных приложениях entry-point может зависеть от нескольких уровней чанков. Простое подключение только `file` недостаточно. Типичная стратегия: * начать с entry * рекурсивно собрать `imports` * исключить дубликаты * сохранить порядок загрузки Пример алгоритма: ```js function collectImports(entryKey, manifest, collected = new Set()) { const entry = manifest[entryKey]; if (!entry) return []; const result = []; if (entry.imports) { for (const imp of entry.imports) { if (!collected.has(imp)) { collected.add(imp); result.push(imp); const subEntry = Object.values(manifest).find( (e) => e.file === imp ); if (subEntry) { const subKey = Object.keys(manifest).find( (k) => manifest[k] === subEntry ); if (subKey) { result.push(...collectImports(subKey, manifest, collected)); } } } } } return result; } ``` --- ## Интеграция с PHP (Bitrix, Laravel, Symfony) В PHP-окружениях manifest используется аналогично Node.js: он читается как JSON-файл и применяется при генерации HTML. ### Пример на PHP ```php $manifest = json_decode(file_get_contents(__DIR__ . '/dist/manifest.json'), true); $entry = $manifest['src/main.js']; echo '
  • '; echo ''; ``` ### Особенности серверной интеграции в PHP: * файл манифеста обычно кешируется в памяти (APCu, Redis) * доступ к JSON минимизируется в production * возможна предобработка манифеста в массив PHP при деплое --- ## Использование manifest в шаблонизаторах В системах шаблонов (Blade, Twig, Smarty) манифест позволяет централизованно подключать ассеты. ### Пример Twig: ```twig {% set entry = manifest['src/main.js'] %} {% for css in entry.css %}
  • {% endfor %} ``` --- ## Кеширование манифеста Поскольку manifest.json может читаться на каждый запрос, его обычно кешируют: ### Стратегии кеширования: * загрузка один раз при старте сервера * in-memory cache * кеширование в OPcache (для PHP) * файловый watch в dev-like окружениях Пример Node.js кеширования: ```js let manifestCache = null; export function getManifest() { if (manifestCache) return manifestCache; manifestCache = JSON.parse( fs.readFileSync('./dist/manifest.json', 'utf-8') ); return manifestCache; } ``` --- ## Сопоставление с index.html трансформацией Vite может работать в режиме, где `index.html` уже содержит шаблонные маркеры. В production сервер заменяет их на реальные значения из manifest. Пример подхода: * входной HTML содержит `` * сервер подставляет `` и `
  • ` --- ## Взаимодействие с asset-ресурсами Помимо JS и CSS, manifest включает: * изображения * шрифты * svg * media-файлы Они обычно находятся в поле `assets`. Пример: ```json { "src/main.js": { "assets": ["assets/logo.3f2c1a.svg"] } } ``` На сервере это используется для генерации корректных ссылок при SSR-рендере компонентов. --- ## Мульти-entry конфигурации При наличии нескольких точек входа manifest становится таблицей маршрутизации ассетов. Пример: ```json { "src/admin.js": { "file": "assets/admin.js" }, "src/app.js": { "file": "assets/app.js" } } ``` Сервер выбирает entry в зависимости от маршрута: * `/admin` → `admin.js` * `/` → `app.js` --- ## Типичные ошибки при работе с manifest Основные проблемы возникают не в самом Vite, а в серверной логике: * использование старого manifest после деплоя * отсутствие обработки imports * кеширование без инвалидации * неверное сопоставление entry-key * попытка использовать dev-структуру в production --- ## Практическая модель интеграции Корректная архитектура обычно включает: * загрузку manifest при старте приложения * слой resolver’а ассетов * шаблонизатор или SSR-рендерер * централизованную функцию получения entry Пример абстракции: ```js function resolveEntry(manifest, name) { const entry = manifest[name]; return { js: entry.file, css: entry.css || [], assets: entry.assets || [] }; } ``` --- ## Поведение при обновлении сборки Каждый новый билд Vite генерирует новый manifest. Это означает: * все ссылки меняются * кеш браузера остаётся валидным * сервер должен мгновенно подхватить новый файл Поэтому важна атомарность деплоя: * сначала кладётся новая сборка * затем переключается ссылка на manifest * старый кеш инвалидируется