Module Development
Charlie descobriu que a lógica de rastreamento, relatório de erros e monitoramento de performance do MegaShop estava espalhada por vários locais. Os outros projetos de Alice e Bob também precisavam das mesmas capacidades. Ao encapsular essas funcionalidades comuns em módulos Nuxt, eles poderiam desenvolvê-las uma vez e reutilizá-las em todos os lugares, e até publicá-las no npm para a comunidade usar.
1. O Que Você Vai Aprender
- Arquitetura de Módulos: defineNuxtModule() + installModule() + Lifecycle Hooks
- Capacidades do Módulo: Injeção de Componentes / Composables / Plugins / Middleware / Rotas do Servidor / Configuração
- Publicação de Módulo: Empacotamento npm + Tipos TypeScript
- Testes de Módulo: @nuxt/test-utils + fixtures
- Guia Prático do Módulo @megashop/analytics do MegaShop
2. Uma História Real de um Arquiteto
(1) Dor: Desenvolvimento Duplicado de Funcionalidades Genéricas
O MegaShop do Charlie precisa de rastreamento—cada visualização de página, cada item adicionado ao carrinho e cada clique deve ser registrado. Ele chama a API manualmente em cinco componentes. O outro projeto de Alice também precisa de rastreamento, então Bob reescreveu o código do zero. O código é repetitivo e inconsistente.
(2) Solução com Módulos Nuxt
Uma vez empacotado como módulo, está pronto para uso imediato—com injeção automática de Composable e API do servidor:
// nuxt.config.ts
modules: ['@megashop/analytics']
(3) Benefícios: Desenvolva Uma Vez, Reutilize em Todos os Lugares
Todos os projetos ganham capacidades de rastreamento simplesmente instalando o módulo. Com Alice, você pode habilitar o rastreamento em qualquer projeto chamando useAnalytics()—sem configuração necessária.
3. Arquitetura de Módulos
(1) Ciclo de Vida do Módulo Nuxt
graph TB
A[defineNuxtModule] --> B[Setup Function]
B --> C[installModule - Dependências]
B --> D[addPlugin - Registrar Plugins]
B --> E[addComposable - Injetar Composables]
B --> F[addServerHandler - Rotas de API]
B --> G[addLayout - Layouts Customizados]
B --> H[addComponent - Auto Componentes]
B --> I[extendConfig - Modificar Config]
J[Nuxt Hooks] --> K[modules:before]
J --> L[modules:done]
J --> M[build:before]
J --> N[build:done]
(1) ▶ Exemplo: Esqueleto de Módulo Mínimo
// 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)
// Registrar plugin
addPlugin(resolve('./runtime/plugin'))
// Expor opções para o runtime
nuxt.options.runtimeConfig.public.analytics = {
enabled: options.enabled,
endpoint: options.endpoint,
debug: options.debug
}
}
})
Saída:
// Execução bem-sucedida
(2) Referência Rápida de Capacidades do Módulo
| Capacidade | API | Descrição |
|---|---|---|
| Registrar Plugin | addPlugin() | Executar automaticamente lógica de inicialização |
| Injetar componente | addComponent() | Auto-importar componentes Vue |
| Injetar Composable | addImports() | Auto-importar funções |
| Adicionar Rota de API | addServerHandler() | Registrar automaticamente rotas do servidor |
| Adicionar Layout | addLayout() | Registrar layout customizado |
| Adicionar middleware | addRouteMiddleware() | Registrar middleware de rota |
| Modificar Configuração | extendConfig() | Modificar Configuração do Nuxt |
| Instalar módulo dependência | installModule() | Instalar outros módulos |
4. Módulo Prático: @megashop/analytics
(1) ▶ Exemplo: Ponto de Entrada do Módulo
// 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. Registrar plugin client para auto-tracking
if (options.trackPageViews || options.trackClicks) {
addPlugin(resolve('./runtime/plugin.client'))
}
// 2. Auto-importar composable useAnalytics
addImports({
name: 'useAnalytics',
from: resolve('./runtime/composables/useAnalytics')
})
// 3. Adicionar API do servidor para receber eventos
addServerHandler({
method: 'post',
route: options.endpoint,
handler: resolve('./runtime/server/api/analytics')
})
// 4. Expor config para o runtime
nuxt.options.runtimeConfig.public.analytics = {
enabled: options.enabled,
endpoint: options.endpoint,
debug: options.debug
}
}
})
Saída:
// Execução bem-sucedida
(2) ▶ Exemplo: Plugin de Runtime
// src/runtime/plugin.client.ts
import { defineNuxtPlugin } from '#app'
export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig().public.analytics
if (!config.enabled) return
// Auto-rastrear visualizações de página
if (config.trackPageViews) {
nuxtApp.hook('page:finish', () => {
useAnalytics().trackPageView(window.location.pathname)
})
}
// Auto-rastrear cliques em elementos data-track
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 })
}
})
}
})
Saída:
// Execução bem-sucedida
(3) ▶ Exemplo: Composable de Runtime
// 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 }
}
Saída:
// Execução bem-sucedida
(4) ▶ Exemplo: API do Servidor de Runtime
// src/runtime/server/api/analytics.ts
import { defineEventHandler, readBody, setHeader } from 'h3'
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// Validar campos obrigatórios
if (!body.event) {
throw createError({ statusCode: 400, message: 'Event name required' })
}
// Armazenar evento (em produção: enviar para serviço de analytics)
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 }
})
Saída:
// Execução bem-sucedida
5. Publicação de Módulo
(1) ▶ Exemplo: Configuração do package.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"
}
}
Saída:
{
"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"
}
}
(2) ▶ Exemplo: Usando em um projeto
// nuxt.config.ts do MegaShop
export default defineNuxtConfig({
modules: [
// Módulo local durante desenvolvimento
'~/modules/analytics',
// Módulo publicado em produção
// '@megashop/analytics'
],
analytics: {
enabled: true,
endpoint: '/api/analytics',
debug: process.env.NODE_ENV === 'development',
trackPageViews: true,
trackClicks: true
}
})
Saída:
// Execução bem-sucedida
6. Testes de Módulo
(1) ▶ Exemplo: Fixture de Teste do Módulo
// 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-registra, verificar plugins do Nuxt
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 disponível
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)
})
})
Saída:
// Execução bem-sucedida
7. Exemplo Completo: Usando @megashop/analytics
<!-- pages/products/[id].vue - Usando módulo de analytics -->
<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-importado pelo módulo
const { trackAddToCart, trackPageView } = useAnalytics()
// Rastreamento manual
onMounted(() => {
trackPageView(`/products/${route.params.id}`)
})
async function handleAddToCart() {
if (!product.value) return
trackAddToCart(product.value.id, product.value.name, product.value.price)
// ... lógica de adicionar ao carrinho
}
</script>
❓ Perguntas Frequentes
runtime no módulo?runtime contém código de runtime—código que é executado apenas quando a aplicação está rodando e não é incluído na configuração de build. Plugins, Composables e server handlers são todos colocados sob runtime/ e não são processados pelo Nuxt Kit.modules/ do projeto e referencie-o em nuxt.config.ts como ~/modules/xxx. Durante o desenvolvimento, use nuxt-module-build --stub para gerar links simbólicos; alterações de código terão efeito em tempo real.installModule('@pinia/nuxt') no setup para instalar módulos dependentes. O Nuxt removerá duplicatas automaticamente.exports e types no package.json, construa usando @nuxt/module-builder, e execute npm publish. Recomendamos usar GitHub Actions para publicação automatizada.exports.types no package.json para apontar para dist/types.d.ts, e os usuários receberão dicas de tipos automaticamente após a instalação.📖 Resumo
- defineNuxtModule: Defina um módulo usando
meta,defaultse a funçãosetup - Capacidades do módulo: injete tudo usando addPlugin, addImports, addServerHandler, addComponent, etc.
- Código de runtime está localizado no diretório
runtime/e não é incluído na fase de configuração de build - Módulo @megashop/analytics: Rastreamento automático + useAnalytics Composable + API do servidor
- Módulos são construídos usando @nuxt/module-builder e publicados com
npm publish
📝 Exercícios
- Exercício Básico (Dificuldade: ⭐): Crie um esqueleto de módulo mínimo, registre um plugin e exiba "Module loaded"
- Exercício Avançado (Dificuldade: ⭐⭐): Desenvolva o módulo @megashop/analytics para implementar o Composable useAnalytics e a API do servidor
- Desafio (Dificuldade: ⭐⭐⭐): Adicione rastreamento automático de visualização de página e rastreamento de clique
data-track, depois escreva testes unitários para verificar a funcionalidade
---|



