添加插件、组件及更多

学习如何从你的模块中注入插件、组件、组合函数和服务器路由。

以下是模块作者常用的一些模式。

修改 Nuxt 配置

模块可以读取并修改 Nuxt 配置。下面是一个启用实验性功能的模块示例。

import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    // 如果还不存在,则创建 `experimental` 对象
    nuxt.options.experimental ||= {}
    nuxt.options.experimental.componentIslands = true
  },
})

当你需要处理更复杂的配置修改时,建议使用 defu

观看 Vue School 视频,了解如何修改 Nuxt 配置。

向运行时暴露选项

由于模块不是应用的运行时一部分,它们的选项也不是。然而,在许多情况下,你可能需要在运行时代码中访问某些模块选项。我们建议使用 Nuxt 的 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 来合并用户提供的公共运行时配置,而不是直接覆盖。

然后你可以在插件、组件或应用中像访问其他运行时配置一样访问你的模块选项:

import { useRuntimeConfig } from '@nuxt/kit'

const options = useRuntimeConfig().public.myModule
请注意不要在公共运行时配置中暴露任何敏感的模块配置,如私有 API 密钥,因为它们将出现在公共包中。
Docs > 3 X > Guide > Going Further > Runtime Config 中查看详情
观看 Vue School 视频,了解如何传递和暴露 Nuxt 模块选项。

添加插件

插件是模块添加运行时逻辑的常见方式。你可以使用 addPlugin 工具从模块中注册它们。

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

export default defineNuxtModule({
  setup (options, nuxt) {
    // 创建解析器以解析相对路径
    const resolver = createResolver(import.meta.url)

    addPlugin(resolver.resolve('./runtime/plugin'))
  },
})
Docs > 3 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)

    // 来自 runtime 目录
    addComponent({
      name: 'MySuperComponent', // 组件在 vue 模板中使用的名字
      export: 'MySuperComponent', // (可选)如果组件是具名导出(非默认导出)
      filePath: resolver.resolve('runtime/app/components/MySuperComponent.vue'),
    })

    // 来自库
    addComponent({
      name: 'MyAwesomeComponent', // 组件在 vue 模板中使用的名字
      export: 'MyAwesomeComponent', // (可选)如果组件是具名导出(非默认导出)
      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 > 3 X > Guide > Modules > Best Practices#prefix Your Exports 中查看详情
请注意,所有通常放置于你的 app/ 文件夹下的组件、页面、组合函数和其他文件,需要放在 runtime/app/ 下。这样才能获得正确的类型检查。
Note that all components, pages, composables and other files that would be normally placed in your app/ folder need to be in runtime/app/. This will mean they can be type checked properly.

Add Composables

如果你的模块需要提供组合函数,可以使用 addImports 工具将它们添加为 Nuxt 的自动导入项。

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

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

    addImports({
      name: 'useComposable', // 组合函数名称
      as: 'useMyComposable', // 可选别名,供使用该模块的应用调用
      from: resolver.resolve('runtime/app/composables/useComposable'), // 组合函数路径
    })
  },
})

也可以传入数组添加多个:

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 > 3 X > Guide > Modules > Best Practices#prefix Your Exports 中查看详情
请注意,所有通常放置于你的 app/ 文件夹下的组件、页面、组合函数和其他文件,需要放在 runtime/app/ 下。这样才能获得正确的类型检查。

Add Keyed Functions

有时,你可能需要在服务端和客户端之间维护状态一致性。例如 Nuxt 内置的 useStateuseAsyncData 组合函数。Nuxt 提供了一种注册此类函数以实现自动注入 key 的方式。

当函数被注册时,如果该函数在调用时传入的参数少于指定的参数数量,Nuxt 的编译器会自动注入一个唯一的 key 作为额外参数。这个 key 在服务端渲染和客户端 hydration 之间保持稳定。

被注入的 key 是由文件路径和调用位置生成的哈希值。

使用 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 配置接受一个对象数组,这些对象包含以下属性:

PropertyTypeDescription
namestring函数名称。使用 'default' 表示默认导出(可调用名称将根据文件名转换为驼峰式推导而来)。
sourcestring函数定义文件的解析路径。支持 Nuxt 别名(~@ 等)
argumentLengthnumber函数所能接收的最大参数数量。当以更少参数调用时,会注入一个唯一的 key。

例如,当 argumentLength: 2

useMyState() // useMyState('$HJiaryoL2y')
useMyState('myKey') // useMyState('myKey', '$HJiaryoL2y')
useMyState('a', 'b') // 不会转换(已经有 2 个参数)
key 注入插件会校验每次函数调用所解析得到的精确导入源。它不会跟随 barrel exports。该函数必须从 source 属性中指定的精确源文件导出。
// ✅ 可行 - 直接导入匹配已配置的 source
import { useMyState } from 'my-module/runtime/composables/state'

// ❌ 不会工作 - 通过 barrel 文件重新导出
import { useMyState } from 'my-module/runtime/composables' // index.ts barrel
函数调用必须能够被静态分析。编译器无法为动态或间接的函数调用注入 key。
import { useMyState } from 'my-module/runtime/composables/state'
import * as composables from 'my-module/runtime/composables/state'

// ✅ 可行 - 直接函数调用
useMyState()

// ✅ 可行 - 在命名空间导入上调用
composables.useMyState()

// ❌ 不会工作 - 动态属性访问
const name = 'useMyState'
composables[name]()

// ❌ 不会工作 - 赋值给变量后调用
const myFn = useMyState
myFn()

// ❌ 不会工作 - 作为回调传入
someFunction(useMyState)

// ❌ 不会工作 - 在嵌套作用域中通过重命名解构
function setup () {
  const { useMyState: localState } = composables
  localState() // 不会转换
}

// ...
请注意,所有通常放置于你的 app/ 文件夹下的组件、页面、组合函数和其他文件,需要放在 runtime/app/ 下。这样才能获得正确的类型检查。

Add Server Routes

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 > 3 X > Guide > Modules > Best Practices#prefix Your Exports 中查看详情

添加其他资源

如果你的模块需要提供其他类型的资源,也可以注入。下面是一个简单的示例模块,通过 Nuxt 的 css 数组注入样式表。

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'))
  },
})

还有一个更高级的示例,通过 NitropublicAssets 选项暴露一个资源目录:

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 年
      })
    })
  },
})

使用其他模块

如果你的模块依赖其他模块,可以使用 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) {
    // 我们可以注入包含 Tailwind 指令的 CSS 文件
    nuxt.options.css.push(resolver.resolve('./runtime/assets/styles.css'))
  },
})
moduleDependencies 选项替代了已废弃的 installModule 函数,确保正确的模块安装顺序和配置合并。