plugins

Nuxt 有一个插件系统,可在创建 Vue 应用时使用 Vue 插件及其他功能。

Nuxt 会自动读取 app/plugins/ 目录下的文件,并在创建 Vue 应用时加载它们。

所有插件都会被自动注册,你不需要在 nuxt.config 中单独添加它们。
你可以在文件名中使用 .server.client 后缀,仅在服务器或客户端加载插件。

已注册的插件

只有目录顶层的文件(或任意子目录内的 index 文件)会被自动注册为插件。

Directory structure
-| plugins/
---| foo.ts      // scanned
---| bar/
-----| baz.ts    // not scanned
-----| foz.vue   // not scanned
-----| index.ts  // currently scanned but deprecated

只有 foo.tsbar/index.ts 会被注册。

要在子目录中添加插件,可以在 nuxt.config.ts 中使用 app/plugins 选项:

nuxt.config.ts
export default defineNuxtConfig({
  plugins: [
    '~/plugins/bar/baz',
    '~/plugins/bar/foz',
  ],
})

创建插件

传入插件的唯一参数是 nuxtApp

plugins/hello.ts
export default defineNuxtPlugin((nuxtApp) => {
  // Doing something with nuxtApp
})

对象语法插件

也可以使用对象语法定义插件,以应对更高级的用例。例如:

plugins/hello.ts
export default defineNuxtPlugin({
  name: 'my-plugin',
  enforce: 'pre', // or 'post'
  async setup (nuxtApp) {
    // this is the equivalent of a normal functional plugin
  },
  hooks: {
    // You can directly register Nuxt app runtime hooks here
    'app:created' () {
      const nuxtApp = useNuxtApp()
      // do something in the hook
    },
  },
  env: {
    // Set this value to `false` if you don't want the plugin to run when rendering server-only or island components.
    islands: true,
  },
})
如果你使用对象语法,这些属性会被静态分析以生成更优化的构建。因此不应在运行时去定义它们。
例如,将 enforce: import.meta.server ? 'pre' : 'post' 这样设置会破坏 Nuxt 能为你的插件进行的任何未来优化。 使用对象语法时,Nuxt 会静态预加载任何钩子监听器,这允许你在不必担心插件注册顺序的情况下定义钩子。

注册顺序

你可以通过在文件名中使用“按字母排序”的编号前缀来控制插件注册的顺序。

Directory structure
plugins/
 | - 01.myPlugin.ts
 | - 02.myOtherPlugin.ts

在这个示例中,02.myOtherPlugin.ts 可以访问由 01.myPlugin.ts 注入的任何内容。

当有插件依赖另一个插件时,这很有用。

如果你不熟悉“按字母排序”的编号,请记住文件名是作为字符串排序的,而不是数值。例如,10.myPlugin.ts 会排在 2.myOtherPlugin.ts 之前。这就是为什么示例中对个位数前面补 0 的原因。

加载策略

并行插件

默认情况下,Nuxt 按顺序加载插件。你可以将插件定义为 parallel,这样 Nuxt 在加载下一个插件前不会等待该插件执行结束。

plugins/my-plugin.ts
export default defineNuxtPlugin({
  name: 'my-plugin',
  parallel: true,
  async setup (nuxtApp) {
    // the next plugin will be executed immediately
  },
})

有依赖的插件

如果一个插件需要在另一个插件之后运行,你可以将该插件的名称添加到 dependsOn 数组中。

plugins/depending-on-my-plugin.ts
export default defineNuxtPlugin({
  name: 'depends-on-my-plugin',
  dependsOn: ['my-plugin'],
  async setup (nuxtApp) {
    // this plugin will wait for the end of `my-plugin`'s execution before it runs
  },
})

使用 Composables

你可以在 Nuxt 插件中使用 composables 以及 utils

app/plugins/hello.ts
export default defineNuxtPlugin((nuxtApp) => {
  const foo = useFoo()
})

但是,请记住这里存在一些限制和差异:

如果一个 composable 依赖于稍后注册的另一个插件,它可能无法工作。
插件是按顺序依次调用,并且在其他所有东西之前调用。你可能会使用一个依赖于尚未被调用的插件的 composable。
如果一个 composable 依赖于 Vue.js 生命周期,它将无法工作。
通常,Vue.js 的 composable 绑定到当前组件实例,而插件只绑定到 nuxtApp 实例。

提供辅助方法

如果你希望在 NuxtApp 实例上提供一个辅助方法,可以在插件中通过 provide 键返回它。

export default defineNuxtPlugin(() => {
  return {
    provide: {
      hello: (msg: string) => `Hello ${msg}!`,
    },
  }
})

然后你可以在组件中使用该辅助方法:

app/components/Hello.vue
<script setup lang="ts">
// alternatively, you can also use it here
const { $hello } = useNuxtApp()
</script>

<template>
  <div>
    {{ $hello('world') }}
  </div>
</template>
我们强烈建议使用 composables 来替代提供全局辅助方法,以避免污染全局命名空间并保持主包入口的小巧。
如果你的插件提供了一个 refcomputed,它在组件的 <template> 中不会被解包。
这是由于 Vue 对非模板顶层的 ref 的处理方式。你可以在 Vue 文档 中阅读更多内容。

插件类型定义

如果你从插件返回了辅助方法,它们会被自动进行类型推断;你会在 useNuxtApp() 的返回值和模板中看到相应的类型。

如果你需要在另一个插件中使用一个已提供的辅助方法,你可以调用 useNuxtApp() 来获取带类型的信息。但一般来说,除非你确定插件的执行顺序,否则应尽量避免这样做。

对于高级用例,你可以像这样声明被注入属性的类型:

index.d.ts
declare module '#app' {
  interface NuxtApp {
    $hello (msg: string): string
  }
}

declare module 'vue' {
  interface ComponentCustomProperties {
    $hello (msg: string): string
  }
}

export {}
如果你使用 WebStorm,可能需要在该问题解决之前,扩展 @vue/runtime-core 的类型声明,详见此问题:WEB-59818

Vue 插件

如果你想使用 Vue 插件,例如使用 vue-gtag 添加 Google Analytics 标签,你可以通过 Nuxt 插件来实现。

首先,安装该 Vue 插件依赖:

npm install --save-dev vue-gtag-next

然后创建一个插件文件:

app/plugins/vue-gtag.client.ts
import VueGtag, { trackRouter } from 'vue-gtag-next'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(VueGtag, {
    property: {
      id: 'GA_MEASUREMENT_ID',
    },
  })
  trackRouter(useRouter())
})

Vue 指令

类似地,你可以在插件中注册自定义的 Vue 指令。

plugins/my-directive.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.directive('focus', {
    mounted (el) {
      el.focus()
    },
    getSSRProps (binding, vnode) {
      // you can provide SSR-specific props here
      return {}
    },
  })
})
如果你注册了一个 Vue 指令,除非你只在某一端渲染时使用它,否则你必须在客户端和服务器端都注册该指令。如果该指令仅在客户端有意义,你可以将其移到 ~/plugins/my-directive.client.ts,并在 ~/plugins/my-directive.server.ts 中为服务端提供一个“占位”指令。
Read more in Vue 文档 - 自定义指令.