plugins

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

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

目录内的所有插件都会自动注册,无需单独将它们添加到你的 nuxt.config 中。
你可以在文件名中使用 .server.client 后缀来指定插件仅在服务端或客户端加载。

已注册插件

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

目录结构
-| plugins/
---| foo.ts      // 被扫描
---| bar/
-----| baz.ts    // 不被扫描
-----| foz.vue   // 不被扫描
-----| index.ts  // 当前会被扫描,但将被弃用

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

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

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

创建插件

插件唯一传入的参数是 nuxtApp

plugins/hello.ts
export default defineNuxtPlugin(nuxtApp => {
  // 使用 nuxtApp 做一些操作
})

对象语法插件

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

plugins/hello.ts
export default defineNuxtPlugin({
  name: 'my-plugin',
  enforce: 'pre', // 或 'post'
  async setup (nuxtApp) {
    // 这等同于普通的函数式插件
  },
  hooks: {
    // 可以直接在这里注册 Nuxt 应用运行时钩子
    'app:created'() {
      const nuxtApp = useNuxtApp()
      // 在钩子中执行某些操作
    }
  },
  env: {
    // 如果你不希望插件在仅服务端渲染或岛屿组件时运行,将该值设置为 `false`。
    islands: true
  }
})
如果使用对象语法,属性会被静态分析以生成更优化的构建,因此不应在运行时定义它们。
例如,设置 enforce: import.meta.server ? 'pre' : 'post' 会阻碍 Nuxt 对你的插件进行未来优化。 使用对象语法时,Nuxt 会静态预加载任何钩子监听器,允许你定义钩子而无需担心插件注册顺序。

注册顺序

你可以通过在文件名前添加“字母数字”编号来控制插件的注册顺序。

目录结构
plugins/
 | - 01.myPlugin.ts
 | - 02.myOtherPlugin.ts

在这个例子中,02.myOtherPlugin.ts 能访问 01.myPlugin.ts 注入的任何内容。

这在某个插件依赖另一个插件时非常有用。

如果你刚接触“字母数字”编号,请记住文件名是按字符串排序的,而非数字值。例如,10.myPlugin.ts 会排在 2.myOtherPlugin.ts 之前。这就是示例中对个位数字前加零的原因。

加载策略

并行插件

默认情况下,Nuxt 顺序加载插件。你可以将插件定义为 parallel,使 Nuxt 不必等到该插件执行完毕就开始加载下一个插件。

plugins/my-plugin.ts
export default defineNuxtPlugin({
  name: 'my-plugin',
  parallel: true,
  async setup (nuxtApp) {
    // 下一个插件将立即执行
  }
})

有依赖的插件

如果一个插件需要等待另一个插件完成才能运行,你可以在 dependsOn 数组中添加它所依赖插件的名称。

plugins/depending-on-my-plugin.ts
export default defineNuxtPlugin({
  name: 'depends-on-my-plugin',
  dependsOn: ['my-plugin'],
  async setup (nuxtApp) {
    // 这个插件会等待 `my-plugin` 执行完毕后才运行
  }
})

使用组合式函数

你可以在 Nuxt 插件中使用 组合式函数工具函数

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

但需要注意一些限制和区别:

如果一个组合式函数依赖于之后注册的插件,可能无法正常工作。
插件会按顺序调用且早于其它操作,你可能会使用一个依赖尚未调用插件的组合式函数。
如果组合式函数依赖 Vue.js 生命周期,它将无法工作。
通常,Vue.js 组合式函数绑定当前组件实例,而插件仅绑定 nuxtApp 实例。

提供帮助函数

如果你想在 NuxtApp 实例上提供帮助函数,可以从插件中通过 provide 属性返回。

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

然后你可以在组件中使用该帮助函数:

components/Hello.vue
<script setup lang="ts">
// 另外,你也可以在这里使用
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,直到 此问题 被解决。

Vue 插件

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

首先,安装 Vue 插件依赖:

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

然后创建一个插件文件:

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) {
      // 你可以在这里提供 SSR 特定的属性
      return {}
    }
  })
})
如果你注册 Vue 指令,必须在客户端和服务端都注册,除非你只在某一端渲染时使用它。如果指令仅在客户端有意义,可以将其移动到 ~/plugins/my-directive.client.ts,并在 ~/plugins/my-directive.server.ts 提供一个“空”指令。
Read more in Vue 文档上的自定义指令.