تطوير الوحدات
اكتشف Charlie أن منطق التتبع والإبلاغ عن الأخطاء ومراقبة الأداء في MegaShop مبعثر في أماكن مختلفة. مشروعات Alice وBob الأخرى تحتاج أيضًا إلى نفس القدرات. بتغليف هذه الميزات الشائعة في وحدات Nuxt، يمكن تطويرها مرة واحدة وإعادة استخدامها في كل مكان، وحتى نشرها على npm لاستخدامها من قبل المجتمع.
1. ما ستتعلمه
- بنية الوحدات: defineNuxtModule() + installModule() + خطافات دورة الحياة
- قدرات الوحدات: حقن المكونات / Composables / الإضافات / الوسائط / توجيه الخادم / التكوين
- نشر الوحدات: تجميع حزم npm + أنواع TypeScript
- اختبار الوحدات: @nuxt/test-utils + fixtures
- دليل عملي لوحدة @megashop/analytics لـ MegaShop
2. قصة حقيقية لمهندس معماري
(1) نقطة الألم: تطوير مكرر للميزات العامة
يحتاج MegaShop الخاص بـ Charlie إلى تتبع—كل مشاهدة صفحة، وكل عنصر مضاف إلى السلة، وكل نقرة يجب تسجيلها. يستدعي API يدويًا في خمسة مكونات. مشروع Alice الآخر يحتاج أيضًا إلى تتبع، لذا أعاد Bob كتابة الكود من الصفر. الكود مكرر وغير متسق.
(2) حل وحدات Nuxt
بمجرد تغليفها كوحدة، تصبح جاهزة للاستخدام فورًا—مع حقن تلقائي لـ Composable وواجهة API للخادم:
// nuxt.config.ts
modules: ['@megashop/analytics']
(3) الفوائد: طوّر مرة واحدة، أعد الاستخدام في كل مكان
جميع المشروعات تحصل على قدرات التتبع بمجرد تثبيت الوحدة. مع Alice، يمكنك تفعيل التتبع في أي مشروع باستدعاء useAnalytics()—بدون أي تكوين.
3. بنية الوحدات
(1) دورة حياة وحدة Nuxt
graph TB
A[defineNuxtModule] --> B[دالة Setup]
B --> C[installModule - التبعيات]
B --> D[addPlugin - تسجيل الإضافات]
B --> E[addComposable - حقن Composables]
B --> F[addServerHandler - مسارات API]
B --> G[addLayout - تخطيطات مخصصة]
B --> H[addComponent - مكونات تلقائية]
B --> I[extendConfig - تعديل التكوين]
J[خطافات Nuxt] --> K[modules:before]
J --> L[modules:done]
J --> M[build:before]
J --> N[build:done]
(1) ▶ مثال: هيكل وحدة بسيط
// 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)
// تسجيل إضافة
addPlugin(resolve('./runtime/plugin'))
// كشف الخيارات لوقت التشغيل
nuxt.options.runtimeConfig.public.analytics = {
enabled: options.enabled,
endpoint: options.endpoint,
debug: options.debug
}
}
})
الناتج:
// التنفيذ ناجح
(2) مرجع سريع لقدرات الوحدات
| القدرة | API | الوصف |
|---|---|---|
| تسجيل إضافة | addPlugin() | تنفيذ منطق التهيئة تلقائيًا |
| حقن مكون | addComponent() | استيراد مكونات Vue تلقائيًا |
| حقن Composable | addImports() | استيراد الدوال تلقائيًا |
| إضافة مسار API | addServerHandler() | تسجيل مسارات الخادم تلقائيًا |
| إضافة تخطيط | addLayout() | تسجيل تخطيط مخصص |
| إضافة وسيط | addRouteMiddleware() | تسجيل وسيط المسار |
| تعديل التكوين | extendConfig() | تعديل تكوين Nuxt |
| تثبيت وحدة تبعية | installModule() | تثبيت وحدات أخرى |
4. وحدة عملية: @megashop/analytics
(1) ▶ مثال: نقطة دخول الوحدة
// 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. تسجيل إضافة العميل للتتبع التلقائي
if (options.trackPageViews || options.trackClicks) {
addPlugin(resolve('./runtime/plugin.client'))
}
// 2. استيراد تلقائي لـ useAnalytics composable
addImports({
name: 'useAnalytics',
from: resolve('./runtime/composables/useAnalytics')
})
// 3. إضافة واجهة API للخادم لاستقبال الأحداث
addServerHandler({
method: 'post',
route: options.endpoint,
handler: resolve('./runtime/server/api/analytics')
})
// 4. كشف التكوين لوقت التشغيل
nuxt.options.runtimeConfig.public.analytics = {
enabled: options.enabled,
endpoint: options.endpoint,
debug: options.debug
}
}
})
الناتج:
// التنفيذ ناجح
(2) ▶ مثال: إضافة وقت التشغيل
// src/runtime/plugin.client.ts
import { defineNuxtPlugin } from '#app'
export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig().public.analytics
if (!config.enabled) return
// تتبع تلقائي لمشاهدات الصفحات
if (config.trackPageViews) {
nuxtApp.hook('page:finish', () => {
useAnalytics().trackPageView(window.location.pathname)
})
}
// تتبع تلقائي للنقرات على عناصر 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 })
}
})
}
})
الناتج:
// التنفيذ ناجح
(3) ▶ مثال: Composable وقت التشغيل
// 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 }
}
الناتج:
// التنفيذ ناجح
(4) ▶ مثال: واجهة API للخادم وقت التشغيل
// src/runtime/server/api/analytics.ts
import { defineEventHandler, readBody, setHeader } from 'h3'
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// التحقق من الحقول المطلوبة
if (!body.event) {
throw createError({ statusCode: 400, message: 'Event name required' })
}
// تخزين الحدث (في الإنتاج: إرسال إلى خدمة التحليلات)
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 }
})
الناتج:
// التنفيذ ناجح
5. نشر الوحدات
(1) ▶ مثال: تكوين 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"
}
}
الناتج:
{
"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
(2) ▶ مثال: الاستخدام في مشروع
// nuxt.config.ts لـ MegaShop
export default defineNuxtConfig({
modules: [
// وحدة محلية أثناء التطوير
'~/modules/analytics',
// وحدة منشورة في الإنتاج
// '@megashop/analytics'
],
analytics: {
enabled: true,
endpoint: '/api/analytics',
debug: process.env.NODE_ENV === 'development',
trackPageViews: true,
trackClicks: true
}
})
الناتج:
// التنفيذ ناجح
6. اختبار الوحدات
(1) ▶ مثال: Fixture اختبار الوحدة
// test/module.test.ts
import { setupTest } from '@nuxt/test-utils'
describe('@megashop/analytics module', () => {
setupTest({
fixture: './test/fixtures/basic',
build: true
})
test('يسجل إضافة التحليلات', () => {
// الإضافة تسجل تلقائيًا، تحقق من إضافات Nuxt
const nuxt = useNuxt()
const hasPlugin = nuxt.options.plugins.some(p => p.src?.includes('analytics'))
expect(hasPlugin).toBe(true)
})
test('يكشف عن useAnalytics composable', async () => {
// الاستيراد التلقائي متاح
const { data } = await useFetch('/api/analytics', {
method: 'POST',
body: { event: 'test', data: {} }
})
expect(data.value).toBeDefined()
})
test('نقطة نهاية التحليلات تقبل الأحداث', async () => {
const response = await $fetch('/api/analytics', {
method: 'POST',
body: { event: 'page_view', data: { path: '/' } }
})
expect(response.success).toBe(true)
})
})
الناتج:
// التنفيذ ناجح
7. مثال شامل: استخدام @megashop/analytics
<!-- pages/products/[id].vue - استخدام وحدة التحليلات -->
<template>
<div v-if="product">
<h1>{{ product.name }}</h1>
<button
@click="handleAddToCart"
data-track="add_to_cart"
>
أضف إلى السلة
</button>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
// useAnalytics مستورد تلقائيًا بواسطة الوحدة
const { trackAddToCart, trackPageView } = useAnalytics()
// تتبع يدوي
onMounted(() => {
trackPageView(`/products/${route.params.id}`)
})
async function handleAddToCart() {
if (!product.value) return
trackAddToCart(product.value.id, product.value.name, product.value.price)
// ... منطق الإضافة إلى السلة
}
</script>
❓ أسئلة شائعة
runtime في الوحدة؟runtime يحتوي على كود وقت التشغيل—كود يُنفذ فقط عند تشغيل التطبيق ولا يُضمّن في تكوين البناء. الإضافات وComposables ومعالجات الخادم توضع جميعها تحت runtime/ ولا تُعالج بواسطة Nuxt Kit.modules/ الخاص بالمشروع وأشر إليها في nuxt.config.ts كـ ~/modules/xxx. أثناء التطوير، استخدم nuxt-module-build --stub لتوليد روابط رمزية؛ تغييرات الكود ستدخل حيز التنفيذ في الوقت الفعلي.installModule('@pinia/nuxt') في setup لتثبيت الوحدات التابعة. ستزيل Nuxt التكرارات تلقائيًا.exports وtypes في package.json، ابنِ باستخدام @nuxt/module-builder، وشغّل npm publish. نوصي باستخدام GitHub Actions للنشر المؤتمت.exports.types في package.json ليشير إلى dist/types.d.ts، وسيحصل المستخدمون تلقائيًا على تلميحات الأنواع بعد التثبيت.📖 ملخص
- defineNuxtModule: تعريف وحدة باستخدام
metaوdefaultsودالةsetup - قدرات الوحدة: حقن كل شيء باستخدام addPlugin وaddImports وaddServerHandler وaddComponent وغيرها
- كود وقت التشغيل موجود في دليل
runtime/ولا يُضمّن في مرحلة تكوين البناء - وحدة @megashop/analytics: تتبع تلقائي + useAnalytics Composable + واجهة API للخادم
- الوحدات تُبنى باستخدام @nuxt/module-builder وتُنشر باستخدام
npm publish
📝 تمارين
- تمرين أساسي (الصعوبة: ⭐): إنشاء هيكل وحدة بسيط، تسجيل إضافة، وطباعة "تم تحميل الوحدة"
- تمرين متقدم (الصعوبة: ⭐⭐): تطوير وحدة @megashop/analytics لتنفيذ useAnalytics Composable وواجهة API للخادم
- تحدي (الصعوبة: ⭐⭐⭐): إضافة تتبع تلقائي لمشاهدات الصفحات وتتبع نقرات
data-track، ثم كتابة اختبارات وحدة للتحقق من الوظائف
---|



