使用钩子和扩展类型

掌握模块中的生命周期钩子、虚拟文件和 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`)
    })
  },
})
阅读更多内容请参考 文档 > 4.x > API > 高级 > 钩子
观看 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 接口或提供您自己的全局类型)。为此,Nuxt 提供了 addTypeTemplate 工具,它既能将模板写入磁盘,又能在生成的 nuxt.d.ts 文件中添加引用。

如果您的模块需要增强 Nuxt 处理的类型,可以使用 addTypeTemplate 来执行此操作

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 'nitropack/types' {
          interface NitroRouteRules extends MyModuleNitroRules {}
          interface NitroRouteConfig extends MyModuleNitroRules {}
        }
        export {}`,
    })
  },
})

如果您需要更精细的控制,可以使用 prepare:types 钩子来注册一个回调,用于注入您的类型。

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

扩展 TypeScript 配置

有多种方法可以从您的模块中扩展用户项目的 TypeScript 配置。

最简单的方法是直接修改 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:typesnitro:prepare:types 钩子来扩展特定类型上下文的 TypeScript 引用,或者像上面的例子一样修改 TypeScript 配置。

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 引用将文件添加到类型上下文中且不受 tsconfig.jsonexclude 选项的影响.

增强类型

Nuxt 会自动将您模块的目录包含在适当的类型上下文中。要增强模块中的类型,您只需根据所增强的类型上下文,将类型声明文件放置在相应的目录中即可。或者,您可以扩展 TypeScript 配置以从任意位置进行增强。

  • my-module/runtime/ - 应用类型上下文(runtime/server 目录除外)
  • my-module/runtime/server/ - 服务器类型上下文
  • my-module/ - Node 类型上下文(runtime/runtime/server 目录除外)
目录结构
-| 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.server.json 外,还会使用 tsconfig.app.json 进行类型检查。

这是必需的,因为 Nuxt 会推断您服务器端点的返回类型,从而在 $fetchuseFetch 中提供响应类型。

当您在路由文件中使用仅限服务器的类型时,这可能会导致问题。例如,如果模块使用 addServerTemplate 创建了仅限服务器的虚拟文件,并且您在 tsconfig.server.json 中为其声明了类型,那么这些类型声明将仅在服务器上下文中可用。当应用上下文对您的服务器路由进行类型检查时,它将无法识别这些仅限服务器的类型并会报告错误。为了解决这个问题,您很遗憾也需要在应用上下文中声明此类类型。