添加插件、组件及更多

了解如何从模块中注入插件、组件、组合式函数(composables)和服务器路由。

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

修改 Nuxt 配置

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

import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup (options, nuxt) {
    // We create the `experimental` object if it doesn't exist yet
    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 密钥),因为它们最终会进入公共构建包中。
文档 > 4 X > 指南 > 深入 > 运行时配置中阅读更多内容。
观看 Vue School 关于传递和暴露 Nuxt 模块选项的视频。

添加插件

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

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

export default defineNuxtModule({
  setup (options, nuxt) {
    // Create resolver to resolve relative paths
    const resolver = createResolver(import.meta.url)

    addPlugin(resolver.resolve('./runtime/plugin'))
  },
})
阅读更多内容请参考 文档 > 4 X > 指南 > 进阶 > Kit

添加组件

如果您的模块需要提供 Vue 组件,可以使用 addComponent 工具将它们作为自动导入项添加,以便 Nuxt 解析。

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

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

    // From the runtime directory
    addComponent({
      name: 'MySuperComponent', // name of the component to be used in vue templates
      export: 'MySuperComponent', // (optional) if the component is a named (rather than default) export
      filePath: resolver.resolve('runtime/app/components/MySuperComponent.vue'),
    })

    // From a library
    addComponent({
      name: 'MyAwesomeComponent', // name of the component to be used in vue templates
      export: 'MyAwesomeComponent', // (optional) if the component is a named (rather than default) export
      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'),
    })
  },
})
强烈建议为您的导出项添加前缀,以避免与用户代码或其他模块冲突。
阅读更多内容请参考 文档 > 4 X > 指南 > 模块 > 最佳实践#prefix Your Exports
请注意,所有通常放在 app/ 文件夹中的组件、页面、组合式函数及其他文件,都需要放置在 runtime/app/ 中。这意味着它们可以被正确地进行类型检查。

添加组合式函数 (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', // name of the composable to be used
      as: 'useMyComposable', // optional alias that will be available for the consuming apps
      from: resolver.resolve('runtime/app/composables/useComposable'), // path of composable
    })
  },
})

多个条目可以以数组形式传递

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'))
  },
})
强烈建议为您的导出项添加前缀,以避免与用户代码或其他模块冲突。
阅读更多内容请参考 文档 > 4 X > 指南 > 模块 > 最佳实践#prefix Your Exports
请注意,所有通常放在 app/ 文件夹中的组件、页面、组合式函数及其他文件,都需要放置在 runtime/app/ 中。这意味着它们可以被正确地进行类型检查。

添加键控函数 (Keyed Functions)

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

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

注入的键是由文件路径和调用位置派生的哈希值。

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

属性类型描述
namestring函数名称。对于默认导出,请使用 'default'(可调用名称将从文件名中以 camelCase 格式派生)。
sourcestring函数定义所在文件的解析路径。支持 Nuxt 别名(~, @ 等)。
argumentLengthnumber函数接受的最大参数数量。当调用参数少于此数量时,会注入一个唯一键。

例如,设置 argumentLength: 2

useMyState() // useMyState('$HJiaryoL2y')
useMyState('myKey') // useMyState('myKey', '$HJiaryoL2y')
useMyState('a', 'b') // not transformed (already has 2 arguments)
键注入插件会验证每个函数调用的确切解析导入源。它不会跟踪 barrel exports(索引导出)。函数必须从 source 属性指定的精确源文件中导出。
// ✅ Works - direct import matches the configured source
import { useMyState } from 'my-module/runtime/composables/state'

// ❌ Won't work - re-exported through a barrel file
import { useMyState } from 'my-module/runtime/composables' // index.ts barrel
函数调用必须是静态可分析的。编译器无法为动态或间接的函数调用注入键。
import { useMyState } from 'my-module/runtime/composables/state'
import * as composables from 'my-module/runtime/composables/state'

// ✅ Works - direct function call
useMyState()

// ✅ Works - called on namespace import
composables.useMyState()

// ❌ Won't work - dynamic property access
const name = 'useMyState'
composables[name]()

// ❌ Won't work - reassigned to a variable
const myFn = useMyState
myFn()

// ❌ Won't work - passed as a callback
someFunction(useMyState)

// ❌ Won't work - destructured with renaming in a nested scope
function setup () {
  const { useMyState: localState } = composables
  localState() // not transformed
}

// ...

添加服务器路由

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

    // Or using a catch all route
    addServerHandler({
      route: '/api/_my-module/files/**:path',
      handler: resolver.resolve('./runtime/server/api/files/[...path].get'),
    })
  },
})
强烈建议为您的服务器路由添加前缀,以避免与用户定义的路由冲突。常见的路径如 /api/auth, /api/login/api/user 可能已经被应用程序使用了。
阅读更多内容请参考 文档 > 4 X > 指南 > 模块 > 最佳实践#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 year
      })
    })
  },
})

使用其他模块

如果您的模块依赖于其他模块,可以使用 moduleDependencies 选项指定它们。这为处理带有版本约束和配置合并的模块依赖提供了一种更健壮的方法。

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

const resolver = createResolver(import.meta.url)

export default defineNuxtModule<ModuleOptions>({
  meta: {
    name: 'my-module',
  },
  moduleDependencies: {
    '@nuxtjs/tailwindcss': {
      // You can specify a version constraint for the module
      version: '>=6',
      // Any configuration that should override `nuxt.options`
      overrides: {
        exposeConfig: true,
      },
      // Any configuration that should be set. It will override module defaults but
      // will not override any configuration set in `nuxt.options`
      defaults: {
        config: {
          darkMode: 'class',
          content: {
            files: [
              resolver.resolve('./runtime/components/**/*.{vue,mjs,ts}'),
              resolver.resolve('./runtime/*.{mjs,js,ts}'),
            ],
          },
        },
      },
    },
  },
  setup (options, nuxt) {
    // We can inject our CSS file which includes Tailwind's directives
    nuxt.options.css.push(resolver.resolve('./runtime/assets/styles.css'))
  },
})
moduleDependencies 选项取代了已弃用的 installModule 函数,并确保了正确的设置顺序和配置合并。