Плагины, компоненты и другое

Как из модуля подключать плагины, компоненты, композаблы и серверные маршруты.

Типовые приёмы для авторов модулей.

Изменение конфигурации Nuxt

Модули могут читать и менять конфигурацию Nuxt. Ниже модуль включает экспериментальную возможность.

import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    // We create the `experimental` object if it doesn't exist yet
    nuxt.options.experimental ||= {}
    nuxt.options.experimental.componentIslands = true
  },
})

Для более сложных правок конфигурации удобен defu.

Видео Vue School: изменение конфигурации Nuxt.

Опции в runtime

Модули не входят в runtime приложения, и их опции тоже. Но часто часть опций модуля нужна в runtime-коде. Рекомендуем отдавать нужное через runtimeConfig.

import { defineNuxtModule } from '@nuxt/kit'
import { defu } from 'defu'

export default defineNuxtModule({
  setup (options, nuxt) {
    nuxt.options.runtimeConfig.public.myModule = defu(nuxt.options.runtimeConfig.public.myModule, {
      foo: options.foo,
    })
  },
})

defu дополняет публичный runtimeConfig, заданный пользователем, а не перезаписывает его.

Опции модуля доступны в плагине, компоненте или приложении так же, как остальной runtimeConfig:

import { useRuntimeConfig } from '@nuxt/kit'

const options = useRuntimeConfig().public.myModule
Не выносите в публичный runtimeConfig секреты модуля (например приватные API-ключи) — они попадут в клиентский бандл.
Узнать больше Docs > 4 X > Guide > Going Further > Runtime Config.
Видео Vue School: передача и экспонирование опций модуля Nuxt.

Плагины

Частый способ добавить логику в runtime — плагины. Регистрируйте их из модуля утилитой addPlugin.

import { addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    // Create resolver to resolve relative paths
    const resolver = createResolver(import.meta.url)

    addPlugin(resolver.resolve('./runtime/plugin'))
  },
})
Узнать больше Docs > 4 X > Guide > Going Further > Kit.

Компоненты

Чтобы модуль поставлял Vue-компоненты, используйте addComponent — они станут автоимпортами для Nuxt.

import { addComponent, createResolver, defineNuxtModule, useRuntimeConfig } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    const resolver = createResolver(import.meta.url)

    // From the runtime directory
    addComponent({
      name: 'MySuperComponent', // name of the component to be used in vue templates
      export: 'MySuperComponent', // (optional) if the component is a named (rather than default) export
      filePath: resolver.resolve('runtime/app/components/MySuperComponent.vue'),
    })

    // From a library
    addComponent({
      name: 'MyAwesomeComponent', // name of the component to be used in vue templates
      export: 'MyAwesomeComponent', // (optional) if the component is a named (rather than default) export
      filePath: '@vue/awesome-components',
    })
  },
})

Весь каталог можно подключить через addComponentsDir.

import { addComponentsDir, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    const resolver = createResolver(import.meta.url)

    addComponentsDir({
      path: resolver.resolve('runtime/app/components'),
    })
  },
})
Сильно рекомендуется префиксовать экспорты, чтобы не пересекаться с кодом пользователя и другими модулями.
Узнать больше Docs > 4 X > Guide > Modules > Best Practices#prefix Your Exports.
Компоненты, страницы, композаблы и прочие файлы, которые обычно лежат в app/, в модуле размещайте в runtime/app/ — так проще проверка типов.

Композаблы

Чтобы модуль поставлял композаблы, используйте addImports для автоимпорта в Nuxt.

import { addImports, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    const resolver = createResolver(import.meta.url)

    addImports({
      name: 'useComposable', // name of the composable to be used
      as: 'useMyComposable', // optional alias that will be available for the consuming apps
      from: resolver.resolve('runtime/app/composables/useComposable'), // path of composable
    })
  },
})

Несколько записей можно передать массивом:

import { addImports, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    const resolver = createResolver(import.meta.url)

    addImports([
      { name: 'useFirstComposable', from: resolver.resolve('runtime/composables/useFirstComposable') },
      { name: 'useSecondComposable', from: resolver.resolve('runtime/composables/useSecondComposable') },
    ])
  },
})

Или подключите целый каталог через addImportsDir.

import { addImportsDir, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    const resolver = createResolver(import.meta.url)

    addImportsDir(resolver.resolve('runtime/composables'))
  },
})
Сильно рекомендуется префиксовать экспорты, чтобы не пересекаться с кодом пользователя и другими модулями.
Узнать больше Docs > 4 X > Guide > Modules > Best Practices#prefix Your Exports.
Компоненты, страницы, композаблы и прочие файлы, которые обычно лежат в app/, в модуле размещайте в runtime/app/ — так проще проверка типов.

Функции с ключом

Иногда нужно согласовывать состояние сервера и клиента — как у встроенных useState или useAsyncData. Nuxt умеет регистрировать такие функции для автоматической подстановки ключа.

Если функция зарегистрирована, компилятор Nuxt при вызове с меньшим числом аргументов добавляет уникальный ключ. Он стабилен между SSR и гидрацией на клиенте.

Подставляемый ключ — хеш от пути файла и места вызова.

Регистрация через опцию keyedComposables:

import { createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    const resolver = createResolver(import.meta.url)

    nuxt.options.optimization.keyedComposables.push({
      name: 'useMyState',
      source: resolver.resolve('./runtime/composables/state'),
      argumentLength: 2,
    })
  },
})

В keyedComposables передаётся массив объектов со свойствами:

СвойствоТипОписание
namestringИмя функции. Для default-экспорта — 'default' (вызываемое имя берётся из имени файла в camelCase).
sourcestringРазрешённый путь к файлу с функцией. Поддерживаются алиасы Nuxt (~, @ и т.д.).
argumentLengthnumberМаксимум аргументов функции. При меньшем числе аргументов подставляется уникальный ключ.

Пример при argumentLength: 2:

useMyState() // useMyState('$HJiaryoL2y')
useMyState('myKey') // useMyState('myKey', '$HJiaryoL2y')
useMyState('a', 'b') // not transformed (already has 2 arguments)
Плагин подстановки ключей сверяет точный разрешённый источник импорта каждого вызова. Barrel-реэкспорты не учитываются. Функция должна экспортироваться из того файла, путь к которому указан в source.
// ✅ Works - direct import matches the configured source
import { useMyState } from 'my-module/runtime/composables/state'

// ❌ Won't work - re-exported through a barrel file
import { useMyState } from 'my-module/runtime/composables' // index.ts barrel
Вызов функции должен быть статически анализируемым. Компилатор не подставит ключи при динамических или косвенных вызовах.
import { useMyState } from 'my-module/runtime/composables/state'
import * as composables from 'my-module/runtime/composables/state'

// ✅ Works - direct function call
useMyState()

// ✅ Works - called on namespace import
composables.useMyState()

// ❌ Won't work - dynamic property access
const name = 'useMyState'
composables[name]()

// ❌ Won't work - reassigned to a variable
const myFn = useMyState
myFn()

// ❌ Won't work - passed as a callback
someFunction(useMyState)

// ❌ Won't work - destructured with renaming in a nested scope
function setup () {
  const { useMyState: localState } = composables
  localState() // not transformed
}

// ...

Middleware маршрутов

import { addRouteMiddleware, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    const resolver = createResolver(import.meta.url)

    // пример глобального middleware маршрута
    addRouteMiddleware({
      global: true,
      name: 'name-of-your-middleware',
      path: resolver.resolve('./runtime/middleware/name-of-your-middleware'),
    })
  },
})

Серверные маршруты

import { addServerHandler, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    const resolver = createResolver(import.meta.url)

    addServerHandler({
      route: '/api/_my-module/hello',
      handler: resolver.resolve('./runtime/server/api/hello/index.get'),
    })
  },
})

Динамический серверный маршрут:

import { addServerHandler, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    const resolver = createResolver(import.meta.url)

    addServerHandler({
      route: '/api/_my-module/hello/:name',
      handler: resolver.resolve('./runtime/server/api/hello/[name].get'),
    })

    // или catch-all маршрут
    addServerHandler({
      route: '/api/_my-module/files/**:path',
      handler: resolver.resolve('./runtime/server/api/files/[...path].get'),
    })
  },
})
Префиксуйте серверные маршруты модуля, чтобы не пересекаться с приложением. Пути вроде /api/auth, /api/login, /api/user часто уже заняты.
Узнать больше Docs > 4 X > Guide > Modules > Best Practices#prefix Your Exports.

Другие ресурсы

Модуль может подключать и другие ресурсы. Простой пример — стиль через массив css в Nuxt.

import { addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    const resolver = createResolver(import.meta.url)

    nuxt.options.css.push(resolver.resolve('./runtime/style.css'))
  },
})

Сложнее — каталог статики через опцию publicAssets Nitro:

import { createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    const resolver = createResolver(import.meta.url)

    nuxt.hook('nitro:config', (nitroConfig) => {
      nitroConfig.publicAssets ||= []
      nitroConfig.publicAssets.push({
        dir: resolver.resolve('./runtime/public'),
        maxAge: 60 * 60 * 24 * 365, // 1 year
      })
    })
  },
})

Другие модули

Если ваш модуль зависит от других, перечислите их в moduleDependencies: версии, слияние и переопределение конфигурации задаются явно.

import { createResolver, defineNuxtModule } from '@nuxt/kit'

const resolver = createResolver(import.meta.url)

export default defineNuxtModule<ModuleOptions>({
  meta: {
    name: 'my-module',
  },
  moduleDependencies: {
    '@nuxtjs/tailwindcss': {
      // ограничение версии модуля
      version: '>=6',
      // перекрывает `nuxt.options`
      overrides: {
        exposeConfig: true,
      },
      // значения по умолчанию модуля; не перекрывают то, что задано в `nuxt.options`
      defaults: {
        config: {
          darkMode: 'class',
          content: {
            files: [
              resolver.resolve('./runtime/components/**/*.{vue,mjs,ts}'),
              resolver.resolve('./runtime/*.{mjs,js,ts}'),
            ],
          },
        },
      },
    },
  },
  setup (options, nuxt) {
    // подключаем CSS с директивами Tailwind
    nuxt.options.css.push(resolver.resolve('./runtime/assets/styles.css'))
  },
})
moduleDependencies заменяет устаревшую installModule и задаёт порядок подключения и слияние конфигурации.