Хуки и расширение типов

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

Ниже — продвинутые приёмы для авторов модулей: хуки, шаблоны и дополнение типов.

Жизненные хуки

Хуки жизненного цикла позволяют расширять почти любой аспект Nuxt. Модули могут подписываться на них из кода или через карту hooks в определении модуля.

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

export default defineNuxtModule({
  // Hook to the `app:error` hook through the `hooks` map
  hooks: {
    'app:error': (err) => {
      console.info(`This error happened: ${err}`)
    },
  },
  setup (options, nuxt) {
    // Programmatically hook to the `pages:extend` hook
    nuxt.hook('pages:extend', (pages) => {
      console.info(`Discovered ${pages.length} pages`)
    })
  },
})
Узнать больше Docs > 4 X > API > Advanced > Hooks.
Видео Vue School: хуки жизненного цикла Nuxt в модулях.
Очистка в модуле

Если модуль открывает ресурсы, обрабатывает что-то или запускает watcher, закройте это по завершении жизненного цикла Nuxt. Для этого есть хук close.
import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    nuxt.hook('close', async (nuxt) => {
      // Your custom code here
    })
  },
})

Свои хуки

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

Если другие модули должны подписаться на ваши хуки, вызывайте их в хуке 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) {
    // Call your hook in `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) {
    // The file is added to Nuxt's internal virtual file system and can be imported from '#build/my-module-feature.mjs'
    addTemplate({
      filename: 'my-module-feature.mjs',
      getContents: () => 'export const myModuleFeature = () => "hello world !"',
    })
  },
})

Для сервера используйте addServerTemplate.

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

export default defineNuxtModule({
  setup (options, nuxt) {
    // The file is added to Nitro's virtual file system and can be imported in the server code from 'my-server-module.mjs'
    addServerTemplate({
      filename: 'my-server-module.mjs',
      getContents: () => 'export const myServerModule = () => "hello world !"',
    })
  },
})

Обновление виртуальных файлов

Чтобы обновить шаблоны/виртуальные файлы, используйте updateTemplates:

nuxt.hook('builder:watch', (event, path) => {
  if (path.includes('my-module-feature.config')) {
    // This will reload the template that you registered
    updateTemplates({ filter: t => t.filename === 'my-module-feature.mjs' })
  }
})

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

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

Если модуль дополняет типы, которыми управляет Nuxt:

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

export default defineNuxtModule({
  setup (options, nuxt) {
    addTypeTemplate({
      filename: 'types/my-module.d.ts',
      getContents: () => `// Generated by my-module
        interface MyModuleNitroRules {
          myModule?: { foo: 'bar' }
        }
        declare module 'nitro/types' {
          interface NitroRouteRules extends MyModuleNitroRules {}
          interface NitroRouteConfig extends MyModuleNitroRules {}
        }
        export {}`,
    })
  },
})

Для более тонкого контроля — хук prepare:types и callback для подключения типов.

const template = addTemplate({ /* template options */ })
nuxt.hook('prepare:types', ({ references }) => {
  references.push({ path: template.dst })
})

Расширение TypeScript {#extend-typescript-config}

Расширить tsconfig проекта из модуля можно по-разному.

Проще всего править конфигурацию Nuxt напрямую:

// extend tsconfig.app.json
nuxt.options.typescript.tsConfig.include ??= []
nuxt.options.typescript.tsConfig.include.push(resolve('./augments.d.ts'))

// extend tsconfig.shared.json
nuxt.options.typescript.sharedTsConfig.include ??= []
nuxt.options.typescript.sharedTsConfig.include.push(resolve('./augments.d.ts'))

// extend tsconfig.node.json
nuxt.options.typescript.nodeTsConfig.include ??= []
nuxt.options.typescript.nodeTsConfig.include.push(resolve('./augments.d.ts'))

// extend 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 }) => {
  // extend app context
  references.push({ path: resolve('./augments.d.ts') })
  // extend shared context
  sharedReferences.push({ path: resolve('./augments.d.ts') })
  // extend node context
  nodeReferences.push({ path: resolve('./augments.d.ts') })
})

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

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

Nuxt сам подключает каталоги модуля в нужные контексты типов. Чтобы дополнить типы, положите файл объявлений в каталог, соответствующий контексту. Или расширьте конфигурацию TypeScript из произвольного пути.

  • my-module/runtime/ — контекст приложения (кроме runtime/server)
  • my-module/runtime/server/ — серверный контекст
  • my-module/ — node-контекст (кроме runtime/ и runtime/server)
Directory Structure
-| my-module/   # node type context
---| runtime/   # app type context
------| augments.app.d.ts
------| server/ # server type context
---------| augments.server.d.ts
---| module.ts
---| augments.node.d.ts

Известные ограничения

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

Серверные маршруты проверяются и по tsconfig.app.json, и по tsconfig.server.json.

Так Nuxt выводит типы ответов для $fetch и useFetch.

Это может ломаться при типах только для сервера в файлах маршрутов. Например, если модуль создаёт серверный виртуальный файл через addServerTemplate и типы для него объявлены только в tsconfig.server.json, при проверке маршрутов в app-контексте эти типы недоступны — появятся ошибки. Обычно такие типы приходится дублировать и в app-контексте.