Nuxt: 模块开发

最后更新:2026-08-26

Charlie 发现 MegaShop 的埋点逻辑、错误上报、性能监控散落在各处。Alice 和 Bob 的其他项目也需要同样的能力。把这些通用功能封装成 Nuxt 模块,一次开发处处复用,还能发布到 npm 给社区用。

1. 你将学到


2. 一个架构师的真实故事

(1) 痛点:通用功能重复开发

Charlie 的 MegaShop 需要埋点——每次页面浏览、每次加购、每次点击都要记录。他在 5 个组件里手动调 API。Alice 的另一个项目也需要埋点,Bob 重新写了一遍。代码重复且不一致。

(2) Nuxt 模块的解法

封装为模块后,安装即用——自动注入 Composable 和 server API:

TYPESCRIPT
// nuxt.config.ts
modules: ['@megashop/analytics']

(3) 收益:一次开发多处复用

所有项目安装模块即可获得埋点能力,Alice 在任何项目用 useAnalytics() 就能埋点,零配置。


3. 模块架构

(1) Nuxt 模块生命周期

100%
graph TB
    A[defineNuxtModule] --> B[Setup Function]
    B --> C[installModule - Dependencies]
    B --> D[addPlugin - Register Plugins]
    B --> E[addComposable - Inject Composables]
    B --> F[addServerHandler - API Routes]
    B --> G[addLayout - Custom Layouts]
    B --> H[addComponent - Auto Components]
    B --> I[extendConfig - Modify Config]
    
    J[Nuxt Hooks] --> K[modules:before]
    J --> L[modules:done]
    J --> M[build:before]
    J --> N[build:done]

▶ 示例:最小模块骨架

TYPESCRIPT
// src/module.ts
import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  meta: {
    name: '@megashop/analytics',
    configKey: 'analytics',
    compatibility: {
      nuxt: '^3.0.0'
    }
  },

  defaults: {
    enabled: true,
    endpoint: '/api/analytics',
    debug: false
  },

  setup(options, nuxt) {
    const { resolve } = createResolver(import.meta.url)

    // Register plugin
    addPlugin(resolve('./runtime/plugin'))

    // Expose options to runtime
    nuxt.options.runtimeConfig.public.analytics = {
      enabled: options.enabled,
      endpoint: options.endpoint,
      debug: options.debug
    }
  }
})

输出:

TEXT 📖 仅展示
// 执行成功

(2) 模块能力速查

能力 API 说明
注册插件 addPlugin() 自动执行初始化逻辑
注入组件 addComponent() 自动导入 Vue 组件
注入 Composable addImports() 自动导入函数
添加 API 路由 addServerHandler() 自动注册 server 路由
添加布局 addLayout() 注册自定义布局
添加中间件 addRouteMiddleware() 注册路由中间件
修改配置 extendConfig() 修改 Nuxt 配置
安装依赖模块 installModule() 安装其他模块

4. 模块实战:@megashop/analytics

▶ 示例:模块入口

TYPESCRIPT
// src/module.ts
import { defineNuxtModule, addPlugin, addImports, addServerHandler, createResolver } from '@nuxt/kit'

export interface ModuleOptions {
  enabled: boolean
  endpoint: string
  debug: boolean
  trackPageViews: boolean
  trackClicks: boolean
}

export default defineNuxtModule<ModuleOptions>({
  meta: {
    name: '@megashop/analytics',
    configKey: 'analytics',
    compatibility: { nuxt: '^3.0.0' }
  },

  defaults: {
    enabled: true,
    endpoint: '/api/analytics',
    debug: false,
    trackPageViews: true,
    trackClicks: true
  },

  setup(options, nuxt) {
    const { resolve } = createResolver(import.meta.url)

    // 1. Register client plugin for auto-tracking
    if (options.trackPageViews || options.trackClicks) {
      addPlugin(resolve('./runtime/plugin.client'))
    }

    // 2. Auto-import useAnalytics composable
    addImports({
      name: 'useAnalytics',
      from: resolve('./runtime/composables/useAnalytics')
    })

    // 3. Add server API for receiving events
    addServerHandler({
      method: 'post',
      route: options.endpoint,
      handler: resolve('./runtime/server/api/analytics')
    })

    // 4. Expose config to runtime
    nuxt.options.runtimeConfig.public.analytics = {
      enabled: options.enabled,
      endpoint: options.endpoint,
      debug: options.debug
    }
  }
})

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例:Runtime Plugin

TYPESCRIPT
// src/runtime/plugin.client.ts
import { defineNuxtPlugin } from '#app'

export default defineNuxtPlugin((nuxtApp) => {
  const config = useRuntimeConfig().public.analytics
  if (!config.enabled) return

  // Auto-track page views
  if (config.trackPageViews) {
    nuxtApp.hook('page:finish', () => {
      useAnalytics().trackPageView(window.location.pathname)
    })
  }

  // Auto-track clicks on data-track elements
  if (config.trackClicks) {
    document.addEventListener('click', (e) => {
      const target = (e.target as HTMLElement).closest('[data-track]')
      if (target) {
        const event = target.getAttribute('data-track') || 'click'
        useAnalytics().track(event, { element: target.tagName })
      }
    })
  }
})

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例:Runtime Composable

TYPESCRIPT
// src/runtime/composables/useAnalytics.ts
export function useAnalytics() {
  const config = useRuntimeConfig().public.analytics

  async function track(event: string, data?: Record<string, any>) {
    if (!config.enabled) return
    if (config.debug) console.log('[Analytics]', event, data)

    await $fetch(config.endpoint, {
      method: 'POST',
      body: { event, data, timestamp: Date.now(), url: import.meta.client ? window.location.href : '' }
    })
  }

  function trackPageView(path: string) {
    track('page_view', { path })
  }

  function trackAddToCart(productId: number, productName: string, price: number) {
    track('add_to_cart', { productId, productName, price, currency: 'USD' })
  }

  function trackPurchase(orderId: string, total: number) {
    track('purchase', { orderId, total, currency: 'USD' })
  }

  return { track, trackPageView, trackAddToCart, trackPurchase }
}

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例:Runtime Server API

TYPESCRIPT
// src/runtime/server/api/analytics.ts
import { defineEventHandler, readBody, setHeader } from 'h3'

export default defineEventHandler(async (event) => {
  const body = await readBody(event)

  // Validate required fields
  if (!body.event) {
    throw createError({ statusCode: 400, message: 'Event name required' })
  }

  // Store event (in production: send to analytics service)
  const storage = useStorage('analytics')
  const key = `event:${Date.now()}:${Math.random().toString(36).slice(2)}`
  await storage.setItem(key, {
    event: body.event,
    data: body.data || {},
    timestamp: body.timestamp || Date.now(),
    url: body.url,
    userAgent: getHeader(event, 'user-agent')
  })

  setHeader(event, 'cache-control', 'no-store')
  return { success: true }
})

输出:

TEXT 📖 仅展示
// 执行成功

5. 模块发布

▶ 示例:package.json 配置

JSON
{
  "name": "@megashop/analytics",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/module.mjs",
  "types": "./dist/types.d.ts",
  "exports": {
    ".": {
      "import": "./dist/module.mjs",
      "require": "./dist/module.cjs",
      "types": "./dist/types.d.ts"
    },
    "./runtime/*": "./dist/runtime/*"
  },
  "files": ["dist"],
  "scripts": {
    "build": "nuxt-module-build",
    "dev": "nuxt-module-build --stub",
    "test": "vitest run",
    "prepublishOnly": "npm run build"
  },
  "peerDependencies": {
    "nuxt": "^3.0.0"
  },
  "devDependencies": {
    "@nuxt/module-builder": "^0.6.0",
    "@nuxt/test-utils": "^3.0.0",
    "nuxt": "^3.12.0"
  }
}

输出:

JSON
{
  "name": "@megashop/analytics",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/module.mjs",
  "types": "./dist/types.d.ts",
  "exports": {
    ".": {
      "import": "./dist/module.mjs",
      "require": "./dist/module.cjs",
      "types": "./dist/types.d.ts"
    },
    "./runtime/*": "./dist/runtime/*"
  },
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "nuxt-module-build",
    "dev": "nuxt-module-build --stub",
    "test": "vitest run",
    "prepublishOnly": "npm run 

▶ 示例:在项目中使用

TYPESCRIPT
// nuxt.config.ts of MegaShop
export default defineNuxtConfig({
  modules: [
    // Local module during development
    '~/modules/analytics',
    // Published module in production
    // '@megashop/analytics'
  ],

  analytics: {
    enabled: true,
    endpoint: '/api/analytics',
    debug: process.env.NODE_ENV === 'development',
    trackPageViews: true,
    trackClicks: true
  }
})

输出:

TEXT 📖 仅展示
// 执行成功

6. 模块测试

▶ 示例:模块测试 fixture

TYPESCRIPT
// test/module.test.ts
import { setupTest } from '@nuxt/test-utils'

describe('@megashop/analytics module', () => {
  setupTest({
    fixture: './test/fixtures/basic',
    build: true
  })

  test('registers analytics plugin', () => {
    // Plugin auto-registers, check Nuxt plugins
    const nuxt = useNuxt()
    const hasPlugin = nuxt.options.plugins.some(p => p.src?.includes('analytics'))
    expect(hasPlugin).toBe(true)
  })

  test('exposes useAnalytics composable', async () => {
    // Auto-import available
    const { data } = await useFetch('/api/analytics', {
      method: 'POST',
      body: { event: 'test', data: {} }
    })
    expect(data.value).toBeDefined()
  })

  test('analytics endpoint accepts events', async () => {
    const response = await $fetch('/api/analytics', {
      method: 'POST',
      body: { event: 'page_view', data: { path: '/' } }
    })
    expect(response.success).toBe(true)
  })
})

输出:

TEXT 📖 仅展示
// 执行成功

7. 综合示例:@megashop/analytics 使用

VUE
<!-- pages/products/[id].vue - Using analytics module -->
<template>
  <div v-if="product">
    <h1>{{ product.name }}</h1>
    <button
      @click="handleAddToCart"
      data-track="add_to_cart"
    >
      Add to Cart
    </button>
  </div>
</template>

<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)

// useAnalytics auto-imported by module
const { trackAddToCart, trackPageView } = useAnalytics()

// Manual tracking
onMounted(() => {
  trackPageView(`/products/${route.params.id}`)
})

async function handleAddToCart() {
  if (!product.value) return
  trackAddToCart(product.value.id, product.value.name, product.value.price)
  // ... add to cart logic
}
</script>

❓ 常见问题

Q 模块和插件有什么区别?
A 模块在构建时执行(配置阶段),可以注入插件/组件/Composable/路由。插件在运行时执行(应用启动),做初始化逻辑。模块可以包含插件。
Q 模块里的 runtime 目录是什么?
A runtime 是运行时代码——只在应用运行时执行,不参与构建配置。插件、Composable、server handler 都放在 runtime/ 下,不会被 Nuxt Kit 处理。
Q 本地模块怎么开发调试?
A 在项目 modules/ 目录下创建模块,nuxt.config.ts 引用 ~/modules/xxx。开发时用 nuxt-module-build --stub 生成软链接,改代码实时生效。
Q 模块能依赖其他模块吗?
A 可以。在 setup 中用 installModule('@pinia/nuxt') 安装依赖模块。Nuxt 会自动去重。
Q 发布到 npm 需要什么?
A package.json 配置 exports + types,用 @nuxt/module-builder 构建,npm publish 发布。推荐用 GitHub Actions 自动发布。
Q 模块的 TypeScript 类型怎么导出?
A @nuxt/module-builder 自动生成类型声明。在 package.json 的 exports.types 指向 dist/types.d.ts,用户安装后自动获得类型提示。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建最小模块骨架,注册一个 plugin 输出 "Module loaded"
  2. 进阶题(难度⭐⭐):开发 @megashop/analytics 模块,实现 useAnalytics Composable + server API
  3. 挑战题(难度⭐⭐⭐):添加自动页面浏览追踪 + data-track 点击追踪,编写模块测试验证功能

---|

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏