الأمان والأداء

مشروع تحسين الأداء (الجزء الثاني)

20 دقيقة الدرس 33 من 35

مشروع تحسين الأداء (الجزء الثاني)

بناءً على الأساسيات التي تم تناولها في الجزء الأول، يستكشف هذا الدرس تقنيات تحسين الأداء المتقدمة، واستراتيجيات الأتمتة، ونُهج المراقبة الشاملة. سنقوم بتطبيق أنماط تحسين متطورة، وإنشاء خطوط أنابيب اختبار الأداء، وإنشاء ثقافة الوعي بالأداء داخل سير عمل التطوير الخاص بك.

استراتيجيات متقدمة لتقسيم الكود

يتجاوز تقسيم الكود مستوى المسار الأساسي. تتطلب التطبيقات الحديثة استراتيجيات متطورة لتحسين تسليم الحزمة وتوقيت التنفيذ. دعنا نستكشف الأنماط المتقدمة التي تحسن بشكل كبير أوقات التحميل الأولية وأداء وقت التشغيل.

تقسيم الكود على أساس المكونات:
<!-- React Component Lazy Loading -->\nconst HeavyChart = lazy(() => import('./components/HeavyChart'));\nconst DataTable = lazy(() => import('./components/DataTable'));\nconst VideoPlayer = lazy(() => import('./components/VideoPlayer'));\n\nfunction Dashboard() {\n  return (\n    <Suspense fallback={<LoadingSpinner />}>\n      <HeavyChart data={chartData} />\n      <DataTable rows={tableData} />\n      <VideoPlayer src={videoUrl} />\n    </Suspense>\n  );\n}\n\n// Vue Component Lazy Loading\nconst AsyncComponent = () => ({\n  component: import('./components/HeavyComponent.vue'),\n  loading: LoadingComponent,\n  error: ErrorComponent,\n  delay: 200,\n  timeout: 10000\n});
الاستيراد الديناميكي مع تعليقات Webpack السحرية:
// Prefetch - يتم التحميل أثناء وقت الخمول\nconst CalendarModule = () => import(\n  /* webpackPrefetch: true */\n  /* webpackChunkName: "calendar" */\n  './modules/Calendar'\n);\n\n// Preload - يتم التحميل بالتوازي مع الأصل\nconst CriticalModule = () => import(\n  /* webpackPreload: true */\n  /* webpackChunkName: "critical" */\n  './modules/Critical'\n);\n\n// التحميل الشرطي بناءً على الجهاز\nif (window.innerWidth > 768) {\n  import(/* webpackChunkName: "desktop-features" */ './desktop-features');\n} else {\n  import(/* webpackChunkName: "mobile-features" */ './mobile-features');\n}
نصيحة: استخدم webpack-bundle-analyzer لتصور تكوين الحزمة الخاصة بك. تكشف هذه الأداة عن فرص التحسين من خلال إظهار الوحدات التي تستهلك أكبر مساحة. قم بتشغيلها بانتظام أثناء التطوير لاكتشاف انتفاخ الحزمة مبكرًا.

تحليل الذاكرة واكتشاف التسرب

تسريبات الذاكرة هي قاتلة صامتة لأداء تطبيقات الويب. فهي تؤدي إلى تدهور تدريجي لتجربة المستخدم ويمكن أن تتسبب في تعطل التطبيقات طويلة الأمد. دعنا ننفذ استراتيجيات تحليل الذاكرة الشاملة للكشف عن التسريبات والقضاء عليها.

أدوات تحليل الذاكرة:
class MemoryProfiler {\n  constructor() {\n    this.snapshots = [];\n    this.baseline = null;\n  }\n\n  takeSnapshot(label) {\n    if (performance.memory) {\n      const snapshot = {\n        label,\n        timestamp: Date.now(),\n        usedJSHeapSize: performance.memory.usedJSHeapSize,\n        totalJSHeapSize: performance.memory.totalJSHeapSize,\n        jsHeapSizeLimit: performance.memory.jsHeapSizeLimit\n      };\n      this.snapshots.push(snapshot);\n      return snapshot;\n    }\n    return null;\n  }\n\n  setBaseline() {\n    this.baseline = this.takeSnapshot('baseline');\n  }\n\n  getMemoryGrowth() {\n    if (!this.baseline || this.snapshots.length < 2) return null;\n    const latest = this.snapshots[this.snapshots.length - 1];\n    return {\n      absolute: latest.usedJSHeapSize - this.baseline.usedJSHeapSize,\n      percentage: ((latest.usedJSHeapSize - this.baseline.usedJSHeapSize) / this.baseline.usedJSHeapSize) * 100,\n      duration: latest.timestamp - this.baseline.timestamp\n    };\n  }\n\n  detectLeak(threshold = 10) {\n    const growth = this.getMemoryGrowth();\n    if (growth && growth.percentage > threshold) {\n      console.warn('تم اكتشاف تسرب محتمل للذاكرة!', growth);\n      return true;\n    }\n    return false;\n  }\n}\n\n// الاستخدام\nconst profiler = new MemoryProfiler();\nprofiler.setBaseline();\n\n// بعد تفاعلات المستخدم\nsetTimeout(() => {\n  profiler.takeSnapshot('after-interaction');\n  profiler.detectLeak();\n}, 5000);
اكتشاف تسرب عقد DOM:
class DOMLeakDetector {\n  constructor() {\n    this.nodeCount = 0;\n    this.listenerCount = 0;\n  }\n\n  countNodes() {\n    return document.getElementsByTagName('*').length;\n  }\n\n  trackListeners() {\n    // لف addEventListener لتتبع المستمعين\n    const original = EventTarget.prototype.addEventListener;\n    const listeners = new Map();\n\n    EventTarget.prototype.addEventListener = function(type, listener, options) {\n      if (!listeners.has(this)) {\n        listeners.set(this, []);\n      }\n      listeners.get(this).push({ type, listener, options });\n      return original.call(this, type, listener, options);\n    };\n\n    return listeners;\n  }\n\n  detectDetachedNodes() {\n    // التحقق من عقد DOM المنفصلة\n    const detachedNodes = [];\n    const allNodes = document.querySelectorAll('*');\n    \n    allNodes.forEach(node => {\n      if (!document.body.contains(node)) {\n        detachedNodes.push(node);\n      }\n    });\n\n    if (detachedNodes.length > 0) {\n      console.warn(`تم العثور على ${detachedNodes.length} عقدة منفصلة`, detachedNodes);\n    }\n    return detachedNodes;\n  }\n}
تحذير: يتطلب تحليل الذاكرة في Chrome DevTools واجهة برمجة تطبيقات الأداء. قم دائمًا باختبار سلوك الذاكرة في ظل ظروف واقعية مع تفاعلات المستخدم الفعلية. قد لا تكشف الاختبارات الآلية عن مشكلات الذاكرة التي تحدث أثناء أنماط الاستخدام الحقيقية.

أتمتة اختبار الأداء

اختبار الأداء اليدوي يستغرق وقتًا طويلاً وغير متسق. يضمن اختبار الأداء الآلي اكتشاف تراجعات الأداء مبكرًا في دورة التطوير، قبل وصولها إلى الإنتاج.

تكوين Lighthouse CI:
// lighthouserc.js\nmodule.exports = {\n  ci: {\n    collect: {\n      numberOfRuns: 3,\n      startServerCommand: 'npm run serve',\n      url: [\n        'http://localhost:3000/',\n        'http://localhost:3000/products',\n        'http://localhost:3000/checkout'\n      ],\n      settings: {\n        preset: 'desktop',\n        throttling: {\n          rttMs: 40,\n          throughputKbps: 10240,\n          cpuSlowdownMultiplier: 1\n        }\n      }\n    },\n    assert: {\n      assertions: {\n        'categories:performance': ['error', { minScore: 0.9 }],\n        'categories:accessibility': ['error', { minScore: 0.9 }],\n        'categories:best-practices': ['error', { minScore: 0.9 }],\n        'categories:seo': ['error', { minScore: 0.9 }],\n        'first-contentful-paint': ['error', { maxNumericValue: 2000 }],\n        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],\n        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],\n        'total-blocking-time': ['error', { maxNumericValue: 300 }]\n      }\n    },\n    upload: {\n      target: 'temporary-public-storage'\n    }\n  }\n};
مجموعة اختبار الأداء المخصصة:
// performance-tests.js\nconst puppeteer = require('puppeteer');\nconst lighthouse = require('lighthouse');\n\nclass PerformanceTestSuite {\n  async runTest(url, config = {}) {\n    const browser = await puppeteer.launch({\n      headless: true,\n      args: ['--no-sandbox', '--disable-dev-shm-usage']\n    });\n\n    const results = {\n      url,\n      timestamp: new Date().toISOString(),\n      metrics: {}\n    };\n\n    try {\n      const page = await browser.newPage();\n      \n      // تفعيل مراقبة الأداء\n      await page.evaluateOnNewDocument(() => {\n        window.performanceMetrics = [];\n      });\n\n      // قياس وقت تحميل الصفحة\n      const startTime = Date.now();\n      await page.goto(url, { waitUntil: 'networkidle2' });\n      const loadTime = Date.now() - startTime;\n\n      // جمع Core Web Vitals\n      const vitals = await page.evaluate(() => {\n        return new Promise((resolve) => {\n          const metrics = {};\n          \n          // LCP\n          new PerformanceObserver((list) => {\n            const entries = list.getEntries();\n            const lastEntry = entries[entries.length - 1];\n            metrics.lcp = lastEntry.renderTime || lastEntry.loadTime;\n          }).observe({ entryTypes: ['largest-contentful-paint'] });\n\n          // FID\n          new PerformanceObserver((list) => {\n            const entries = list.getEntries();\n            entries.forEach(entry => {\n              metrics.fid = entry.processingStart - entry.startTime;\n            });\n          }).observe({ entryTypes: ['first-input'] });\n\n          // CLS\n          let clsScore = 0;\n          new PerformanceObserver((list) => {\n            list.getEntries().forEach(entry => {\n              if (!entry.hadRecentInput) {\n                clsScore += entry.value;\n              }\n            });\n            metrics.cls = clsScore;\n          }).observe({ entryTypes: ['layout-shift'] });\n\n          setTimeout(() => resolve(metrics), 3000);\n        });\n      });\n\n      results.metrics = {\n        loadTime,\n        ...vitals,\n        passed: loadTime < 3000 && vitals.lcp < 2500 && vitals.cls < 0.1\n      };\n\n    } catch (error) {\n      results.error = error.message;\n    } finally {\n      await browser.close();\n    }\n\n    return results;\n  }\n\n  async runSuite(urls) {\n    const results = [];\n    for (const url of urls) {\n      const result = await this.runTest(url);\n      results.push(result);\n    }\n    return results;\n  }\n}\n\n// الاستخدام في CI/CD\nconst suite = new PerformanceTestSuite();\nconst results = await suite.runSuite([\n  'https://example.com',\n  'https://example.com/products',\n  'https://example.com/checkout'\n]);\n\nif (results.some(r => !r.metrics.passed)) {\n  console.error('فشلت اختبارات الأداء!');\n  process.exit(1);\n}

مراقبة المستخدم الحقيقي (RUM)

يخبرك الاختبار الاصطناعي بجزء فقط من القصة. تلتقط مراقبة المستخدم الحقيقي تجارب المستخدم الفعلية عبر أجهزة وشبكات ومواقع جغرافية متنوعة. هذه البيانات لا تقدر بثمن لفهم الأداء في العالم الحقيقي.

تنفيذ RUM:
class RealUserMonitoring {\n  constructor(config = {}) {\n    this.apiEndpoint = config.apiEndpoint || '/api/rum';\n    this.sampleRate = config.sampleRate || 0.1;\n    this.sessionId = this.generateSessionId();\n    this.metrics = [];\n  }\n\n  init() {\n    if (Math.random() > this.sampleRate) return;\n\n    // جمع توقيت التنقل\n    window.addEventListener('load', () => {\n      this.collectNavigationTiming();\n      this.collectResourceTiming();\n      this.observeWebVitals();\n    });\n\n    // إرسال البيانات قبل إلغاء تحميل الصفحة\n    window.addEventListener('beforeunload', () => {\n      this.sendBeacon();\n    });\n  }\n\n  collectNavigationTiming() {\n    const timing = performance.getEntriesByType('navigation')[0];\n    if (!timing) return;\n\n    this.addMetric('navigation', {\n      dns: timing.domainLookupEnd - timing.domainLookupStart,\n      tcp: timing.connectEnd - timing.connectStart,\n      ttfb: timing.responseStart - timing.requestStart,\n      download: timing.responseEnd - timing.responseStart,\n      domInteractive: timing.domInteractive - timing.fetchStart,\n      domComplete: timing.domComplete - timing.fetchStart,\n      loadComplete: timing.loadEventEnd - timing.fetchStart\n    });\n  }\n\n  collectResourceTiming() {\n    const resources = performance.getEntriesByType('resource');\n    const byType = {};\n\n    resources.forEach(resource => {\n      const type = this.getResourceType(resource.name);\n      if (!byType[type]) byType[type] = [];\n      \n      byType[type].push({\n        name: resource.name,\n        duration: resource.duration,\n        size: resource.transferSize\n      });\n    });\n\n    this.addMetric('resources', byType);\n  }\n\n  observeWebVitals() {\n    // LCP\n    new PerformanceObserver((list) => {\n      const entries = list.getEntries();\n      const lastEntry = entries[entries.length - 1];\n      this.addMetric('lcp', {\n        value: lastEntry.renderTime || lastEntry.loadTime,\n        element: lastEntry.element?.tagName\n      });\n    }).observe({ entryTypes: ['largest-contentful-paint'] });\n\n    // FID\n    new PerformanceObserver((list) => {\n      list.getEntries().forEach(entry => {\n        this.addMetric('fid', {\n          value: entry.processingStart - entry.startTime,\n          name: entry.name\n        });\n      });\n    }).observe({ entryTypes: ['first-input'] });\n\n    // CLS\n    let clsScore = 0;\n    new PerformanceObserver((list) => {\n      list.getEntries().forEach(entry => {\n        if (!entry.hadRecentInput) {\n          clsScore += entry.value;\n        }\n      });\n      this.addMetric('cls', { value: clsScore });\n    }).observe({ entryTypes: ['layout-shift'] });\n  }\n\n  addMetric(type, data) {\n    this.metrics.push({\n      type,\n      data,\n      timestamp: Date.now(),\n      url: window.location.href,\n      userAgent: navigator.userAgent\n    });\n  }\n\n  sendBeacon() {\n    const payload = JSON.stringify({\n      sessionId: this.sessionId,\n      metrics: this.metrics,\n      context: {\n        url: window.location.href,\n        referrer: document.referrer,\n        viewport: `${window.innerWidth}x${window.innerHeight}`,\n        connection: navigator.connection?.effectiveType\n      }\n    });\n\n    navigator.sendBeacon(this.apiEndpoint, payload);\n  }\n\n  getResourceType(url) {\n    if (url.match(/\.(js)$/)) return 'script';\n    if (url.match(/\.(css)$/)) return 'stylesheet';\n    if (url.match(/\.(png|jpg|jpeg|gif|svg|webp)$/)) return 'image';\n    if (url.match(/\.(woff|woff2|ttf|eot)$/)) return 'font';\n    return 'other';\n  }\n\n  generateSessionId() {\n    return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n  }\n}\n\n// تهيئة RUM\nconst rum = new RealUserMonitoring({\n  apiEndpoint: 'https://api.example.com/rum',\n  sampleRate: 0.1 // مراقبة 10٪ من المستخدمين\n});\nrum.init();
ملاحظة: أخذ عينات RUM أمر بالغ الأهمية للمواقع ذات الحركة المرورية العالية. تولد مراقبة 100٪ من المستخدمين كميات ضخمة من البيانات ويمكن أن تؤثر على الأداء. ابدأ بأخذ عينات من 5-10٪ واضبطها بناءً على أنماط الحركة واحتياجات البيانات.

اختبار تراجع الأداء

يمكن أن تتراجع تحسينات الأداء بسهولة أثناء التطوير السريع. يكتشف اختبار التراجع الآلي تدهور الأداء قبل وصوله إلى الإنتاج، مع الحفاظ على المكاسب التي تم تحقيقها بشق الأنفس من جهود التحسين.

تكوين ميزانية الأداء:
// performance-budget.json\n{\n  "budgets": [\n    {\n      "path": "/*",\n      "resourceSizes": [\n        { "resourceType": "script", "budget": 300 },\n        { "resourceType": "stylesheet", "budget": 100 },\n        { "resourceType": "image", "budget": 500 },\n        { "resourceType": "font", "budget": 150 },\n        { "resourceType": "total", "budget": 1000 }\n      ],\n      "timings": [\n        { "metric": "first-contentful-paint", "budget": 2000 },\n        { "metric": "largest-contentful-paint", "budget": 2500 },\n        { "metric": "time-to-interactive", "budget": 3500 },\n        { "metric": "total-blocking-time", "budget": 300 },\n        { "metric": "cumulative-layout-shift", "budget": 0.1 }\n      ]\n    },\n    {\n      "path": "/products/*",\n      "resourceSizes": [\n        { "resourceType": "script", "budget": 350 },\n        { "resourceType": "image", "budget": 800 }\n      ]\n    }\n  ]\n}
نص اختبار التراجع:
const fs = require('fs');\nconst path = require('path');\n\nclass PerformanceRegressionTester {\n  constructor(baselinePath, budgetPath) {\n    this.baseline = JSON.parse(fs.readFileSync(baselinePath, 'utf8'));\n    this.budget = JSON.parse(fs.readFileSync(budgetPath, 'utf8'));\n  }\n\n  compareMetrics(current) {\n    const regressions = [];\n    const improvements = [];\n\n    // المقارنة مع الأساس\n    Object.keys(this.baseline.metrics).forEach(metric => {\n      const baselineValue = this.baseline.metrics[metric];\n      const currentValue = current.metrics[metric];\n      const diff = currentValue - baselineValue;\n      const percentChange = (diff / baselineValue) * 100;\n\n      if (percentChange > 10) {\n        regressions.push({\n          metric,\n          baseline: baselineValue,\n          current: currentValue,\n          change: percentChange.toFixed(2) + '%'\n        });\n      } else if (percentChange < -10) {\n        improvements.push({\n          metric,\n          baseline: baselineValue,\n          current: currentValue,\n          change: percentChange.toFixed(2) + '%'\n        });\n      }\n    });\n\n    // التحقق من الميزانية\n    const budgetViolations = [];\n    this.budget.budgets.forEach(budget => {\n      budget.timings?.forEach(timing => {\n        const value = current.metrics[timing.metric];\n        if (value > timing.budget) {\n          budgetViolations.push({\n            metric: timing.metric,\n            value,\n            budget: timing.budget,\n            exceeded: value - timing.budget\n          });\n        }\n      });\n    });\n\n    return { regressions, improvements, budgetViolations };\n  }\n\n  generateReport(comparison) {\n    let report = '\n=== تقرير تراجع الأداء ===\n\n';\n\n    if (comparison.regressions.length > 0) {\n      report += 'تم اكتشاف تراجعات:\n';\n      comparison.regressions.forEach(r => {\n        report += `  ❌ ${r.metric}: ${r.baseline}ms → ${r.current}ms (${r.change})\n`;\n      });\n      report += '\n';\n    }\n\n    if (comparison.budgetViolations.length > 0) {\n      report += 'انتهاكات الميزانية:\n';\n      comparison.budgetViolations.forEach(v => {\n        report += `  ⚠️  ${v.metric}: ${v.value}ms تتجاوز الميزانية البالغة ${v.budget}ms بمقدار ${v.exceeded}ms\n`;\n      });\n      report += '\n';\n    }\n\n    if (comparison.improvements.length > 0) {\n      report += 'التحسينات:\n';\n      comparison.improvements.forEach(i => {\n        report += `  ✅ ${i.metric}: ${i.baseline}ms → ${i.current}ms (${i.change})\n`;\n      });\n    }\n\n    return report;\n  }\n}\n\n// الاستخدام في خط أنابيب CI/CD\nconst tester = new PerformanceRegressionTester(\n  './baseline.json',\n  './performance-budget.json'\n);\n\nconst currentMetrics = await runPerformanceTests();\nconst comparison = tester.compareMetrics(currentMetrics);\nconst report = tester.generateReport(comparison);\n\nconsole.log(report);\n\nif (comparison.regressions.length > 0 || comparison.budgetViolations.length > 0) {\n  process.exit(1);\n}

قائمة التحقق النهائية للتحسين

تضمن قائمة التحقق الشاملة هذه عدم تفويت أي فرصة للتحسين. استخدمها للمراجعات النهائية قبل الإصدارات الرئيسية أو أثناء عمليات تدقيق الأداء.

تمرين: إكمال تدقيق التحسين
تحسين الواجهة الأمامية:
☐ الصور محسّنة (تنسيق WebP/AVIF، صور متجاوبة، تحميل بطيء)
☐ CSS محسّن (مصغّر، CSS الحرج مضمّن، إزالة CSS غير المستخدم)
☐ JavaScript محسّن (مصغّر، تقليم الأشجار، تقسيم الكود)
☐ الخطوط محسّنة (تنسيق WOFF2، font-display: swap، التجزئة)
☐ الموارد مضغوطة (تمكين Brotli/Gzip)
☐ تمكين HTTP/2 أو HTTP/3
☐ تكوين CDN للأصول الثابتة
☐ تنفيذ Service Worker للدعم دون اتصال
☐ تنفيذ تلميحات الموارد (preconnect، prefetch، preload)

تحسين الخادم:
☐ استعلامات قاعدة البيانات محسّنة (مفهرسة، بدون استعلامات N+1)
☐ تنفيذ التخزين المؤقت للاستجابة (Redis/Memcached)
☐ استجابات API مضغوطة
☐ تكوين تجميع الاتصالات
☐ وظائف الخلفية للعمليات الثقيلة
☐ تنفيذ تحديد المعدل
☐ التخزين المؤقت من جانب الخادم (OPcache، APCu)

المراقبة والاختبار:
☐ مراقبة الأداء نشطة (RUM + اصطناعي)
☐ تكوين التنبيهات لعتبات المقاييس
☐ تحديد ميزانيات الأداء وفرضها
☐ اختبارات الأداء الآلية في CI/CD
☐ جدولة عمليات تدقيق الأداء المنتظمة
☐ تتبع وتحسين Core Web Vitals

التنفيذ:
قم بإجراء تدقيق كامل لتطبيقك باستخدام قائمة التحقق هذه. قم بتوثيق الحالة الحالية، وتحديد الفجوات، وترتيب أولويات التحسينات، وإنشاء خطة عمل. شارك نتائج التدقيق مع فريقك وأنشئ عمليات مراجعة الأداء المستمرة.
نصيحة احترافية: أنشئ لوحة معلومات الأداء التي تعرض المقاييس الرئيسية في الوقت الفعلي. اجعلها مرئية للفريق بأكمله. عندما يتمكن الجميع من رؤية تأثير تغييراتهم على الأداء، فإنه يخلق ثقافة الوعي بالأداء والتحسين المستمر.

تحسين الأداء المتقدم رحلة مستمرة، وليس وجهة. من خلال تطبيق استراتيجيات المراقبة والاختبار والتحسين المتطورة هذه، فإنك تنشئ أساسًا لأداء عالٍ مستدام. المفتاح هو جعل الأداء جزءًا أساسيًا من ثقافة التطوير الخاصة بك، وليس فكرة لاحقة. مع الاختبار الآلي والمراقبة الشاملة والميزانيات الواضحة، فإنك تضمن أن يظل تطبيقك سريعًا ومستجيبًا مع تطوره.