Создание сборщика
Сборщик (builder) — часть Nuxt, которая собирает приложение в бандлы. Nuxt поставляет три официальных сборщика: Vite (по умолчанию), webpack и Rspack. Выбор — через опцию builder или свой сборщик.
Здесь — как сборщик встроен в сборку Nuxt, какой контракт он должен выполнять и как написать свой.
Что делает сборщик
Nuxt разделяет что собирать и как это собирать.
Ядро Nuxt (контекст nuxt из Как устроен Nuxt) и ваши модули формируют виртуальное приложение: точки входа, таблицу маршрутов, плагины, реестр компонентов и остальную виртуальную файловую систему под #build. Сборщик берёт это виртуальное приложение и превращает его в реальные JavaScript- и CSS-бандлы; в разработке поднимает dev-сервер, отдаёт их и делает hot reload.
Конкретно сборщик отвечает за:
- Сборку клиентского бандла (браузер) и при включённом SSR — серверного (точка входа SSR-приложения).
- Артефакты, которые нужны серверному рантайму для рендера и гидратации: client manifest, карта стилей по компонентам и т.д. (см. Контракт выходных данных сборки).
- В разработке — dev-сервер и перезагрузку при изменении сборки.
Сам сервер для деплоя собирает Nitro через @nuxt/nitro-server, а не сборщик. Сборщик передаёт выходные данные Nitro по типизированному контракту; Nitro упаковывает их в финальный .output.
Интерфейс сборщика
Сборщик — объект, реализующий интерфейс NuxtBuilder. Обязателен только метод bundle:
import type { Nuxt } from '@nuxt/schema'
export interface NuxtBuilder {
bundle: (nuxt: Nuxt) => Promise<void>
/**
* Optional. When the user opts in via `experimental.watcher: 'builder'`,
* Nuxt calls this instead of starting its own dev file watcher, letting
* the builder reuse its own watcher. The builder should register a
* `nuxt.hook('close', ...)` to clean up.
*/
setupWatcher?: (nuxt: Nuxt) => Promise<void> | void
}
Nuxt выбирает активный сборщик из опции builder. Можно указать модуль с default-экспортом NuxtBuilder или inline-объект:
export default defineNuxtConfig({
// a package that exports `{ bundle }`
builder: '@nuxt/vite-builder',
})
import type { NuxtBuilder } from '@nuxt/schema'
const myBuilder: NuxtBuilder = {
async bundle (nuxt) {
// ...
},
}
export default defineNuxtConfig({
builder: myBuilder,
})
Nuxt вызывает bundle(nuxt) один раз при nuxt build и nuxt dev, после генерации виртуального приложения и хука build:before. Nuxt оборачивает ваш bundle: любая ошибка автоматически вызывает хук build:error.
@nuxt/vite-builder, @nuxt/webpack-builder, @nuxt/rspack-builder). Свой сборщик работает через общий контракт ниже, но считается legacy-путём (не Vite Environment).Жизненный цикл сборки
При nuxt build или nuxt dev Nuxt:
- Создаёт контекст
nuxt, запускает модули, заполняетnuxt.optionsи хуки сборки. - Генерирует виртуальное приложение (шаблоны, таблицу маршрутов, плагины) в виртуальную ФС
#build. - Вызывает
build:before. - Разрешает сборщик и вызывает
builder.bundle(nuxt). Здесь работает ваш сборщик. - Вызывает
build:done; в production закрывает экземплярnuxt.
Реализация bundle обычно ветвится по nuxt.options.dev:
- В production — доводит клиентскую и (если
nuxt.options.ssr) серверную сборку до конца, пишет артефакты вnuxt.options.buildDirи регистрирует их как build outputs. - В development — поднимает dev-сервер, запускает watching-сборку, назначает
nuxt.serverи продолжает работу.
Сборщик почти целиком общается с остальным Nuxt через хуки. Конфигурацию сборки он читает из nuxt.options и при необходимости даёт модулям расширять конфиг bundler'а.
Расширение bundler'а модулями
Модули влияют на бандл через хелперы Nuxt Kit. Сборщик должен их учитывать:
addVitePlugin/addWebpackPlugin— плагины конкретного bundler'а.addBuildPlugin— фабрика unplugin, один плагин для всех сборщиков.extendViteConfig/extendWebpackConfig— изменение итогового конфига bundler'а.
Официальные сборщики также эмитят свои хуки (например, vite:extendConfig, vite:serverCreated, webpack:config), чтобы модули и Nitro участвовали в сборке. Свой сборщик может эмитить свои хуки, но совместимость с серверным рантаймом Nuxt обеспечивает контракт выходных данных ниже.
Контракт выходных данных сборки
Серверный рантайм (@nuxt/nitro-server) не знает, какой сборщик собрал приложение. Он импортирует каждый артефакт через стабильный subpath nuxt/*; активный сборщик заполняет их через nuxt.buildOutputs. Это контракт выходных данных сборки.
Контракт описан интерфейсом NuxtBuildOutputs в @nuxt/schema:
export interface NuxtBuildOutputs {
/** Module body re-exporting the SSR app entry. */
serverEntry: () => string | Promise<string>
/** Path to the emitted per-component SSR styles map, or `undefined` when no inline styles are produced. */
ssrStyles: string | undefined
/** Serialized client manifest for `vue-bundle-renderer`. */
clientManifest: () => string | Promise<string>
/** Serialized precomputed client dependency data for `vue-bundle-renderer`. */
clientPrecomputed: () => string | Promise<string>
/** Module body exporting the hashed entry chunk filename for import maps. */
entryChunkName: () => string | Promise<string>
/** Module body exporting the entry module IDs used for inline style extraction. */
entryIds: () => string | Promise<string>
}
Каждый ключ соответствует subpath nuxt/*, который импортирует серверный рантайм:
| Выход сборки | Subpath | Импортируется как |
|---|---|---|
serverEntry | nuxt/entry | фабрика SSR-приложения для createRenderer |
clientManifest | nuxt/manifest | client manifest для vue-bundle-renderer |
clientPrecomputed | nuxt/precomputed | предвычисленные данные зависимостей |
ssrStyles | nuxt/styles | карта inline-стилей по компонентам |
entryChunkName | nuxt/entry-chunk | имя entry-чанка с хешем (import map) |
entryIds | nuxt/entry-ids | ID entry-модулей для извлечения стилей |
У каждого subpath nuxt/* в пакете nuxt есть заглушка по умолчанию, поэтому серверный рантайм типизируется и собирается ещё до запуска сборщика. Сборщик переопределяет заглушку, задавая соответствующий build output; это значение подставляется на этапе сборки вместо заглушки.
Два вида build output
Ключи бывают двух форм:
- Провайдеры значений (
serverEntry,clientManifest,clientPrecomputed,entryChunkName,entryIds) — функции, возвращающие тело модуля строкой. Строка встраивается в серверный бандл дословно, поэтому не должна зависеть от расположения файла на диске. Например,serverEntryвозвращает тело с re-export собранной SSR-точки входа по абсолютному specifier:setBuildOutput('serverEntry', () => `export { default } from ${JSON.stringify(serverEntryURL)}`) - Путь к эмитированному файлу (
ssrStyles) — абсолютный путь (не код) к реальному модулю, который эмитировал сборщик. Импортnuxt/stylesв рантайме разрешается к этому файлу, чтобы bundler деплоя разрешал относительные sibling-импорты карты стилей относительно расположения файла. Если моделировать это строкой кода, контекст каталога теряется и относительные импорты ломаются, поэтому здесь — путь:setBuildOutput('ssrStyles', resolve(serverOutDir, 'styles.mjs'))
ОставьтеssrStylesкакundefined(значение по умолчанию), если сборка не даёт inline-стилей; рантайм использует пустую карту стилей.
Установка build outputs
Используйте хелпер setBuildOutput из @nuxt/kit:
import { setBuildOutput } from '@nuxt/kit'
setBuildOutput('clientManifest', () => 'export default ' + serializedManifest)
setBuildOutput пишет в nuxt.buildOutputs[key]. Внутри плагина bundler'а, где уже есть экземпляр nuxt, можно присвоить nuxt.buildOutputs[key] напрямую; setBuildOutput — удобство для кода, который получает nuxt через useNuxt().
Провайдер может быть асинхронным и читается лениво, когда серверная сборка разрешает соответствующий импорт nuxt/*. Сборщик может зарегистрировать провайдер заранее (например, до завершения клиентской сборки) и вернуть финальное значение, когда данные готовы.
Минимальный пример
Каркас сборщика, выполняющего контракт для production-сборки:
import { resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import { setBuildOutput } from '@nuxt/kit'
import type { NuxtBuilder } from '@nuxt/schema'
export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
const serverDir = resolve(nuxt.options.buildDir, 'dist/server')
// ...run your client and server bundles here, writing artifacts to disk...
const { serializedClientManifest } = await runBundles(nuxt, serverDir)
if (nuxt.options.ssr) {
// Point `nuxt/entry` at the built SSR app entry.
const serverEntryURL = pathToFileURL(resolve(serverDir, 'server.mjs')).href
setBuildOutput('serverEntry', () => `export { default } from ${JSON.stringify(serverEntryURL)}`)
// Provide the client manifest produced by your client build.
setBuildOutput('clientManifest', () => `export default ${serializedClientManifest}`)
// If you emit a per-component styles map alongside its CSS chunks:
setBuildOutput('ssrStyles', resolve(serverDir, 'styles.mjs'))
}
}
serverEntry по умолчанию — no-op app, остальные выходы не используются.Dev-сервер
В разработке сборщик также отвечает за отдачу приложения и hot reload. Важны две вещи:
nuxt.server— работающий dev-сервер. Его использует CLI Nuxt; официальные сборщики экспонируютhandler(Node request listener),fetch(webfetchhandler) и методыreload/close. Свой dev-сервер или делегирование Nitro (createDevServerизnitro/builder) — на усмотрение сборщика.- Перезагрузки — через
nuxt.server.reload()по завершении компиляции. Официальные сборщики эмитят хукcompiled(например,vite:compiled,webpack:compiled), на который подписывается серверная интеграция.
В dev build outputs обычно указывают на живые in-memory источники, а не на файлы на диске. SSR-точка входа может отдаваться из in-memory выхода bundler'а, client manifest — вычисляться из dev module graph, а не читаться из nuxt.options.buildDir.
Сборщики и Vite Environment API
Контракт выше намеренно не привязан к конкретному сборщику и работает и для legacy-пути (каждый сборщик гоняет свою сборку, Nitro отдельно собирает деплой через Rollup), и для Vite Environment API, где Nitro работает как Vite environment.
Для своего сборщика достаточно общего контракта. Интеграция Vite Environment API (experimental.nitroViteEnvironment) — только для @nuxt/vite-builder; остальные сборщики, включая кастомные, идут по legacy-пути Nitro Rollup.