Хуки и типы

Хуки жизненного цикла, виртуальные файлы и объявления TypeScript в модулях.

Продвинутые приёмы: хуки, шаблоны и расширение типов.

Хуки жизненного цикла

Хуки позволяют вмешиваться почти во все этапы Nuxt. Модуль может подписаться через карту hooks или в setup.

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

export default defineNuxtModule({
  // Подписка на хук `app:error` через карту `hooks`
  hooks: {
    'app:error': (err) => {
      console.info(`Произошла ошибка: ${err}`)
    },
  },
  setup (options, nuxt) {
    // Программная подписка на хук `pages:extend`
    nuxt.hook('pages:extend', (pages) => {
      console.info(`Найдено страниц: ${pages.length}`)
    })
  },
})
Узнать больше Docs > 3 X > API > Advanced > Hooks.
Видео Vue School про хуки в модулях.
Очистка в модуле

Если модуль открывает вотчеры или долгоживущие ресурсы, закройте их на close.
import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    nuxt.hook('close', async (nuxt) => {
      // Ваш код очистки здесь
    })
  },
})

Свои хуки

Модуль может объявлять и вызывать собственные хуки — удобно для расширяемости.

Если другие модули должны подписаться, вызывайте хук в modules:done: к этому моменту они уже успели зарегистрировать слушатели в setup.

// пример: my-module/module.ts
import { defineNuxtModule } from '@nuxt/kit'

export interface ModuleHooks {
  'my-module:custom-hook': (payload: { foo: string }) => void
}

export default defineNuxtModule({
  setup (options, nuxt) {
    // Вызовите свой хук в `modules:done`
    nuxt.hook('modules:done', async () => {
      const payload = { foo: 'bar' }
      await nuxt.callHook('my-module:custom-hook', payload)
    })
  },
})

Виртуальные файлы

Чтобы добавить виртуальный файл, импортируемый в приложении пользователя, используйте addTemplate.

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

export default defineNuxtModule({
  setup (options, nuxt) {
    // Файл попадает во внутреннюю виртуальную ФС Nuxt; импорт из '#build/my-module-feature.mjs'
    addTemplate({
      filename: 'my-module-feature.mjs',
      getContents: () => 'export const myModuleFeature = () => "привет, мир!"',
    })
  },
})

Для сервера — addServerTemplate.

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

export default defineNuxtModule({
  setup (options, nuxt) {
    // Файл в виртуальной ФС Nitro; в серверном коде — импорт из 'my-server-module.mjs'
    addServerTemplate({
      filename: 'my-server-module.mjs',
      getContents: () => 'export const myServerModule = () => "привет, мир!"',
    })
  },
})

Обновление шаблонов

При изменении конфигурации виртуальных файлов вызывайте updateTemplates:

nuxt.hook('builder:watch', (event, path) => {
  if (path.includes('my-module-feature.config')) {
    // Перезагрузит зарегистрированный шаблон
    updateTemplates({ filter: t => t.filename === 'my-module-feature.mjs' })
  }
})

Объявления типов

Чтобы добавить типы в проект пользователя (расширить интерфейсы Nuxt или объявить свои глобальные типы), используйте addTypeTemplate: шаблон попадает на диск и в ссылки генерируемого nuxt.d.ts.

Расширение типов Nitro:

import { addTemplate, addTypeTemplate, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    addTypeTemplate({
      filename: 'types/my-module.d.ts',
      getContents: () => `// Сгенерировано модулем my-module
        interface MyModuleNitroRules {
          myModule?: { foo: 'bar' }
        }
        declare module 'nitropack/types' {
          interface NitroRouteRules extends MyModuleNitroRules {}
          interface NitroRouteConfig extends MyModuleNitroRules {}
        }
        export {}`,
    })
  },
})

Тоньше — хук prepare:types:

const template = addTemplate({ /* параметры шаблона */ })
nuxt.hook('prepare:types', ({ references }) => {
  references.push({ path: template.dst })
})

Расширение tsconfig

Проще всего править опции Nuxt:

// расширить tsconfig.app.json
nuxt.options.typescript.tsConfig.include ??= []
nuxt.options.typescript.tsConfig.include.push(resolve('./augments.d.ts'))

// расширить tsconfig.shared.json
nuxt.options.typescript.sharedTsConfig.include ??= []
nuxt.options.typescript.sharedTsConfig.include.push(resolve('./augments.d.ts'))

// расширить tsconfig.node.json
nuxt.options.typescript.nodeTsConfig.include ??= []
nuxt.options.typescript.nodeTsConfig.include.push(resolve('./augments.d.ts'))

// расширить tsconfig.server.json
nuxt.options.nitro.typescript ??= {}
nuxt.options.nitro.typescript.tsConfig ??= {}
nuxt.options.nitro.typescript.tsConfig.include ??= []
nuxt.options.nitro.typescript.tsConfig.include.push(resolve('./augments.d.ts'))

Или хуки prepare:types и nitro:prepare:types:

nuxt.hook('prepare:types', ({ references, sharedReferences, nodeReferences }) => {
  // контекст приложения
  references.push({ path: resolve('./augments.d.ts') })
  // общий контекст
  sharedReferences.push({ path: resolve('./augments.d.ts') })
  // контекст Node
  nodeReferences.push({ path: resolve('./augments.d.ts') })
})

nuxt.hook('nitro:prepare:types', ({ references }) => {
  // серверный контекст
  references.push({ path: resolve('./augments.d.ts') })
})
Ссылки TypeScript подключают файлы в контекст типов независимо от exclude в tsconfig.json.

Дополнение типов

Nuxt сам подхватывает каталоги модуля в нужных контекстах типов. Файлы объявлений кладите в каталог, соответствующий контексту, или расширьте tsconfig.

  • my-module/runtime/ — контекст приложения (кроме runtime/server)
  • my-module/runtime/server/ — серверный контекст
  • my-module/ — Node-контекст (кроме runtime/ и runtime/server/)
Структура каталогов
-| my-module/   # контекст типов Node
---| runtime/   # контекст типов приложения
------| augments.app.d.ts
------| server/ # контекст типов сервера
---------| augments.server.d.ts
---| module.ts
---| augments.node.d.ts

Ограничения

Типы серверных маршрутов в контексте приложения

Серверные маршруты проверяются и по tsconfig.app.json, и по tsconfig.server.json, потому что Nuxt выводит типы ответов для $fetch и useFetch.

Только серверные типы в файлах маршрутов могут ломать проверку: если модуль добавляет виртуальный файл через addServerTemplate и типы объявлены только в серверном tsconfig, при проверке в контексте приложения их не видно. Тогда типы нужно продублировать (или сделать видимыми) и в контексте приложения.