Рендеринг на стороне сервера
Примечание
SSR конкретно относится к интерфейсным платформам (например, React, Preact, Vue и Svelte), которые поддерживают запуск одного и того же приложения в Node.js, предварительный рендеринг его в HTML и, наконец, его гидратацию на клиенте. Если вам нужна интеграция с традиционными серверными фреймворками, ознакомьтесь с Руководством по интеграции с серверной частью.
В следующем руководстве также предполагается, что у вас есть опыт работы с SSR в выбранной вами среде, и оно будет сосредоточено только на деталях интеграции, специфичных для Vite.
API низкого уровня
Это низкоуровневый API, предназначенный для авторов библиотек и фреймворков. Если ваша цель — создать приложение, обязательно сначала ознакомьтесь с плагинами и инструментами SSR более высокого уровня в разделе Awesome Vite SSR. Тем не менее, многие приложения успешно создаются непосредственно поверх собственного низкоуровневого API Vite.
В настоящее время Vite работает над улучшенным API SSR с Environment API. Ознакомьтесь со ссылкой для получения более подробной информации.
Помощь
Если у вас есть вопросы, сообщество обычно помогает на канале Vite Discord #ssr.
Примеры проектов
Vite предоставляет встроенную поддержку рендеринга на стороне сервера (SSR). create-vite-extra
содержит примеры настроек SSR, которые вы можете использовать в качестве ссылок для этого руководства:
You can also scaffold these projects locally by running create-vite
and choose Others > create-vite-extra
under the framework option.
Исходная структура
Типичное приложение SSR будет иметь следующую структуру исходного файла:
- index.html
- server.js # главный сервер приложений
- src/
- main.js # экспортирует код приложения, независимый от env (универсальный)
- entry-client.js # монтирует приложение к элементу DOM
- entry-server.js # визуализирует приложение с помощью API SSR платформы
index.html
должен будет ссылаться на entry-client.js
и включать заполнитель, где должна быть введена разметка, отображаемая сервером:
<div id="app"><!--ssr-outlet--></div>
<script type="module" src="/src/entry-client.js"></script>
Вы можете использовать любой заполнитель вместо <!--ssr-outlet-->
, если его можно точно заменить.
Условная логика
Если вам нужно выполнить условную логику на основе SSR и клиента, вы можете использовать
if (import.meta.env.SSR) {
// ... server only logic
}
Это статически заменяется во время сборки, поэтому это позволяет tree-shaking неиспользуемых branches.
Настройка сервера разработки
При создании приложения SSR вы, вероятно, захотите иметь полный контроль над своим основным сервером и отделить Vite от производственной среды. Поэтому рекомендуется использовать Vite в режиме мидлвара. Вот пример с express:
server.js
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import express from 'express'
import { createServer as createViteServer } from 'vite'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
async function createServer() {
const app = express()
// Create Vite server in middleware mode and configure the app type as
// 'custom', disabling Vite's own HTML serving logic so parent server
// can take control
const vite = await createViteServer({
server: { middlewareMode: true },
appType: 'custom'
})
// Use vite's connect instance as middleware. If you use your own
// express router (express.Router()), you should use router.use
// When the server restarts (for example after the user modifies
// vite.config.js), `vite.middlewares` is still going to be the same
// reference (with a new internal stack of Vite and plugin-injected
// middlewares). The following is valid even after restarts.
app.use(vite.middlewares)
app.use('*', async (req, res) => {
// serve index.html - we will tackle this next
})
app.listen(5173)
}
createServer()
Здесь vite
— это экземпляр ViteDevServer. vite.middlewares
— это экземпляр Connect, который можно использовать в качестве мидлвара в любой совместимой с Connect инфраструктуре Node.js.
Следующим шагом является реализация обработчика *
для обслуживания отображаемого сервером HTML:
app.use('*', async (req, res, next) => {
const url = req.originalUrl
try {
// 1. Read index.html
let template = fs.readFileSync(
path.resolve(__dirname, 'index.html'),
'utf-8',
)
// 2. Apply Vite HTML transforms. This injects the Vite HMR client,
// and also applies HTML transforms from Vite plugins, e.g. global
// preambles from @vitejs/plugin-react
template = await vite.transformIndexHtml(url, template)
// 3. Load the server entry. ssrLoadModule automatically transforms
// ESM source code to be usable in Node.js! There is no bundling
// required, and provides efficient invalidation similar to HMR.
const { render } = await vite.ssrLoadModule('/src/entry-server.js')
// 4. render the app HTML. This assumes entry-server.js's exported
// `render` function calls appropriate framework SSR APIs,
// e.g. ReactDOMServer.renderToString()
const appHtml = await render(url)
// 5. Inject the app-rendered HTML into the template.
const html = template.replace(`<!--ssr-outlet-->`, appHtml)
// 6. Send the rendered HTML back.
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
// If an error is caught, let Vite fix the stack trace so it maps back
// to your actual source code.
vite.ssrFixStacktrace(e)
next(e)
}
})
Сценарий dev
в package.json
также следует изменить, чтобы вместо него использовался сценарий сервера:
"scripts": {
- "dev": "vite"
+ "dev": "node server"
}
Сборка для продакшена
Чтобы отправить проект SSR в производство, нам необходимо:
- Произведите сборку клиента как обычно;
- Создайте сборку SSR, которую можно напрямую загрузить через
import()
, чтобы нам не приходилось проходить через VitessrLoadModule
;
Наши скрипты в package.json
будут выглядеть так:
{
"scripts": {
"dev": "node server",
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --outDir dist/server --ssr src/entry-server.js"
}
}
Обратите внимание на флаг --ssr
, который указывает, что это сборка SSR. Также следует указать запись SSR.
Затем в server.js
нам нужно добавить некоторую логику, специфичную для продакшена, проверив process.env.NODE_ENV
:
Вместо чтения корневого
index.html
используйтеdist/client/index.html
в качестве шаблона, так как он содержит правильные ссылки на ресурсы для сборки клиента.Вместо
await vite.ssrLoadModule('/src/entry-server.js')
используйтеimport('./dist/server/entry-server.js')
(этот файл является результатом сборки SSR).Переместите создание и все использование сервера разработки
vite
за условные ветки только для разработки, затем добавьте мидлвар для обслуживания статических файлов для обслуживания файлов изdist/client
.
Смотрите примеры проектов для рабочей настройки.
Создание директив предварительной загрузки
vite build
поддерживает флаг --ssrManifest
, который будет генерировать .vite/ssr-manifest.json
в выходном каталоге сборки:
- "build:client": "vite build --outDir dist/client",
+ "build:client": "vite build --outDir dist/client --ssrManifest",
Приведенный выше сценарий теперь сгенерирует dist/client/.vite/ssr-manifest.json
для сборки клиента (да, манифест SSR генерируется из сборки клиента, поскольку мы хотим сопоставить идентификаторы модулей с файлами клиента). Манифест содержит сопоставления идентификаторов модулей с соответствующими фрагментами и файлами ресурсов.
Чтобы использовать манифест, фреймворки должны предоставить способ сбора идентификаторов модулей компонентов, которые использовались во время вызова рендеринга сервера.
@vitejs/plugin-vue
поддерживает это из коробки и автоматически регистрирует используемые идентификаторы модулей компонентов в связанном контексте Vue SSR:
// src/entry-server.js
const ctx = {}
const html = await vueServerRenderer.renderToString(app, ctx)
// ctx.modules is now a Set of module IDs that were used during the render
В продакшен ветке server.js
нам нужно прочитать и передать манифест функции render
, экспортируемой src/entry-server.js
. Это предоставит нам достаточно информации для отображения директив предварительной загрузки для файлов, используемых асинхронными маршрутами! Полный пример см. в источнике демонстрационной версии. Вы также можете использовать эту информацию для 103 Early Hints.
Предварительный рендеринг / SSG
Если маршруты и данные, необходимые для определенных маршрутов, известны заранее, мы можем предварительно преобразовать эти маршруты в статический HTML, используя ту же логику, что и производственная SSR. Это также можно рассматривать как форму создания статических сайтов (SSG). Смотрите демонстрационный скрипт предварительного рендеринга для рабочего примера.
Внешний SSR
Зависимости «экстернализуются» из системы модулей преобразования Vite SSR по умолчанию при запуске SSR. Это ускоряет как разработку, так и сборку.
Если зависимость должна быть преобразована конвейером Vite, например, потому что функции Vite используются в них нетранспилированными, их можно добавить в ssr.noExternal
.
Для связанных зависимостей они по умолчанию не экстернализуются, чтобы использовать преимущества HMR Vite. Если это нежелательно, например, для проверки зависимостей, как если бы они не были связаны, вы можете добавить его в ssr.external
.
Работа с псевдонимами
Если вы настроили псевдонимы, которые перенаправляют один пакет на другой, вы можете вместо этого использовать псевдонимы фактических пакетов node_modules
, чтобы они работали для внешних зависимостей SSR. Оба Yarn и pnpm поддерживают псевдонимы через префикс npm:
.
Логика плагина, специфичная для SSR
Некоторые фреймворки, такие как Vue или Svelte, компилируют компоненты в разные форматы в зависимости от клиента и SSR. Для поддержки условных преобразований Vite передает дополнительное свойство ssr
в объект options
следующих хуков плагина:
resolveId
load
transform
Пример:
export function mySSRPlugin() {
return {
name: 'my-ssr',
transform(code, id, options) {
if (options?.ssr) {
// perform ssr-specific transform...
}
},
}
}
Объект параметров в load
и transform
является необязательным, в настоящее время накопительный пакет не использует этот объект, но может расширить эти хуки дополнительными метаданными в будущем.
Примечание
До Vite 2.7 об этом сообщалось обработчикам плагинов с позиционным параметром ssr
вместо использования объекта options
. Все основные фреймворки и плагины обновлены, но вы можете найти устаревшие сообщения, используя предыдущий API.
Цель SSR
Целью по умолчанию для сборки SSR является среда узла, но вы также можете запустить сервер в веб-воркере. Разрешение входа пакетов отличается для каждой платформы. Вы можете настроить цель как Web Worker, используя для ssr.target
значение 'webworker'
.
SSR бандл
В некоторых случаях, таких как среды выполнения webworker
, вы можете захотеть объединить сборку SSR в один файл JavaScript. Вы можете включить это поведение, установив для ssr.noExternal
значение true
. Это сделает две вещи:
- Рассматривать все зависимости как
noExternal
- Выдавать ошибку, если какие-либо встроенные модули Node.js импортированы
Условия решения SSR
По умолчанию разрешение записи пакета будет использовать условия, установленные в resolve.conditions
для сборки SSR. Вы можете использовать ssr.resolve.conditions
и ssr.resolve.externalConditions
, чтобы настроить это поведение.
Vite CLI
Команды CLI $ vite dev
и $ vite preview
также можно использовать для приложений SSR. Вы можете добавить слой мидлваров SSR на сервер разработки с помощью configureServer
и на сервер предварительного просмотра с помощью configurePreviewServer
.
Примечание
Используйте пост-хук, чтобы ваше мидлвар SSR запускал мидлвар после Vite.