使用 Hooks 并扩展类型

掌握模块中的生命周期钩子、虚拟文件和 TypeScript 声明。

以下是编写模块的一些高级模式,包括钩子、模板和类型增强。

使用生命周期钩子

生命周期钩子 允许你扩展 Nuxt 几乎所有方面。模块可以通过编程方式或在定义中的 hooks 映射来绑定这些钩子。

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

export default defineNuxtModule({
  // 通过 `hooks` 映射绑定 `app:error` 钩子
  hooks: {
    'app:error': (err) => {
      console.info(`发生了这个错误: ${err}`)
    },
  },
  setup (options, nuxt) {
    // 以编程方式绑定 `pages:extend` 钩子
    nuxt.hook('pages:extend', (pages) => {
      console.info(`发现了 ${pages.length} 个页面`)
    })
  },
})
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) => {
      // 在此处添加你的自定义代码
    })
  },
})

创建自定义钩子

模块还可以定义和调用自己的钩子,这是一种让你的模块可扩展的强大模式。

如果你期望其他模块能够订阅你的模块钩子,应该在 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 = () => "hello world !"',
    })
  },
})

对于服务器端,应使用 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 = () => "hello world !"',
    })
  },
})

更新虚拟文件

如果你需要更新你的模板/虚拟文件,可以利用 updateTemplates 工具,如下所示:

nuxt.hook('builder:watch', (event, path) => {
  if (path.includes('my-module-feature.config')) {
    // 这将重新加载你注册的模板
    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: () => `// 由 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 })
})

扩展 TypeScript 配置

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

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

nuxt.hook('prepare:types', ({ references, sharedReferences, nodeReferences }) => {
  // 扩展 app 上下文
  references.push({ path: resolve('./augments.d.ts') })
  // 扩展 shared 上下文
  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 引用会将文件添加到类型上下文中,不会受到 tsconfig.jsonexclude 选项的影响

类型增强

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

  • my-module/runtime/ - app 类型上下文(不包括 runtime/server 目录)
  • my-module/runtime/server/ - 服务器类型上下文
  • my-module/ - node 类型上下文(不包括 runtime/runtime/server 目录)
目录结构
-| my-module/   # node 类型上下文
---| runtime/   # app 类型上下文
------| augments.app.d.ts
------| server/ # 服务器类型上下文
---------| augments.server.d.ts
---| module.ts
---| augments.node.d.ts