أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

Vite Bundle Split: نزّل JavaScript الأولي من 1.8MB لـ 620KB

📅 ٢٦ أبريل ٢٠٢٦⏱ 4 دقائق قراءة
Vite Bundle Split: نزّل JavaScript الأولي من 1.8MB لـ 620KB

Vite Bundle Split: قلّل JavaScript الأولي بدون إعادة كتابة التطبيق

هتكسب تحميل أول أسرع لو فصلت الكود الثقيل في Vite بدل ما ترسله لكل المستخدمين من أول زيارة. في المثال ده هننزل JavaScript الأولي من 1.8MB إلى 620KB، مع trade-off واضح: أول فتح للصفحة الثقيلة هيحتاج request إضافي.

مستوى القارئ: متوسط

المشكلة باختصار

الطريقة الشائعة الغلط إنك تسيب كل imports في main.tsx أو في route رئيسي واحد. الطريقة دي بتفشل لما التطبيق يكبر: صفحة التقارير، محرر Markdown، مكتبة charts، وPDF viewer يدخلوا في نفس bundle حتى لو المستخدم فتح صفحة Dashboard بسيطة.

الافتراض إن عندك تطبيق React أو Vue مبني بـ Vite، وفيه route ثقيل لا يفتحه كل المستخدمين. مثال واقعي: SaaS داخلي عنده 40 ألف زيارة يوميًا، لكن صفحة التقارير المتقدمة لا يفتحها إلا 12% من المستخدمين. إرسال مكتبة charts لكل الناس هنا تكلفة واضحة.

مخطط يوضح تقسيم bundle في Vite بين الملف الأولي وchunk يتم تحميله عند فتح الصفحة الثقيلة

مثال بسيط قبل الشرح الدقيق

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

في الواجهة نفس الفكرة بالظبط. الصفحة الأساسية تحتاج shell خفيف: navigation، auth state، وجدول بسيط. أما editor أو charting engine فده ملف منفصل يتم تحميله لما route يتفتح. علميًا، ده اسمه code splitting: تقسيم كود التطبيق إلى chunks أصغر، بحيث المتصفح يحمّل الجزء المطلوب أولًا ويؤجل الباقي.

الخطوة الأولى: افصل route ثقيل بـ dynamic import

ابدأ بأعلى route في الحجم. لا تبدأ بتقسيم 20 component صغير. أفضل طريقة إنك تفتح bundle analyzer أو تقيس ملفات dist/assets، ثم تختار route واضح مثل /reports.

TSX

// src/router.tsx
import { lazy, Suspense } from 'react';
import Dashboard from './pages/Dashboard';

const ReportsPage = lazy(() => import('./pages/ReportsPage'));

export function AppRoutes() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/" element={<Dashboard />} />
        <Route path="/reports" element={<ReportsPage />} />
      </Routes>
    </Suspense>
  );
}

الـ trade-off هنا إن أول دخول إلى /reports هيطلب chunk إضافي. المكسب إن المستخدم الذي لا يفتح التقارير لن يدفع تكلفة chart editor من البداية. لو عندك route يفتحه أقل من 30% من المستخدمين، غالبًا المكسب يستاهل.

الخطوة الثانية: امنع vendor chunk من التحول لصندوق واحد ضخم

بعد dynamic import، ممكن تلاقي vendor.js لسه كبير. السبب إن المكتبات كلها اتجمعت في chunk واحد. استخدم manualChunks بحذر لفصل المكتبات الثقيلة ذات الاستخدام المحدد.

TypeScript

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    chunkSizeWarningLimit: 700,
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes('node_modules/recharts')) return 'charts';
          if (id.includes('node_modules/monaco-editor')) return 'editor';
          if (id.includes('node_modules/react')) return 'react-vendor';
        },
      },
    },
  },
});

مهم: لا تعمل vendor واحد لكل node_modules بشكل أعمى. ده ممكن يثبت chunk ضخم في أول تحميل أو يزود cache invalidation. الأفضل تفصل المكتبات الثقيلة التي لها route أو feature واضح.

القياس قبل وبعد

في حالة قياس تقديرية مبنية على تطبيق تقارير متوسط، كانت النتيجة كالتالي: قبل التقسيم، أول route يحمّل حوالي 1.8MB JavaScript غير مضغوط، وزمن parse/compile على جهاز متوسط حوالي 640ms. بعد فصل التقارير والمحرر، initial JS نزل إلى 620KB، وزمن parse/compile نزل إلى 240ms. أول فتح لصفحة التقارير نفسها صار 980ms بدل 2.1s لأن chunk أصبح أوضح وأقل ازدحامًا.

رسم أعمدة يقارن حجم JavaScript وزمن التحليل قبل وبعد تقسيم bundle في Vite

قِس بالأوامر دي قبل وبعد. لا تعتمد على الإحساس.

Bash

npm run build
npx vite-bundle-visualizer --open

# قياس الملفات الناتجة
find dist/assets -name "*.js" -maxdepth 1 -exec ls -lh {} \;

# اختبار Lighthouse من CLI لو متاح عندك
npx lighthouse http://localhost:4173 --view

ما يجب الانتباه له

  • زمن أول زيارة للصفحة الثقيلة: قد يزيد request إضافي. عالجه بـ prefetch بعد استقرار الصفحة الأساسية لو المستخدم غالبًا هيفتحها.
  • تقسيم زائد: 30 chunk صغير ممكن يكون أسوأ من 4 chunks واضحة، خصوصًا على شبكات ضعيفة.
  • side effects: بعض المكتبات تفترض ترتيب تحميل معين. Rollup نفسه ينبه إن manual chunks قد تغير سلوك التطبيق لو فيه side effects مبكرة.
  • cache strategy: chunk ثابت للمكتبات الكبيرة جيد للكاش، لكن تقسيم خاطئ ممكن يخلي كل deploy يكسر كاش أكبر من اللازم.

متى لا تستخدم هذه الطريقة

لا تستخدمها لو التطبيق صغير أصلًا وinitial JS أقل من 250KB بعد الضغط. ولا تستخدمها لو كل المستخدمين يفتحون نفس الصفحة الثقيلة في أول 5 ثواني. في الحالة دي أنت لم تؤجل التكلفة، أنت فقط نقلتها إلى request ثاني. كذلك تجنب manualChunks لو الفريق لا يملك قياسًا واضحًا؛ التقسيم اليدوي بدون أرقام يتحول لصيانة مزعجة.

مصادر

  • Vite Build Options: https://vite.dev/config/build-options.html
  • Rollup output.manualChunks: https://rollupjs.org/configuration-options/#output-manualchunks
  • MDN dynamic import: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import

الخطوة التالية

افتح build الحالي، طلع أكبر 3 ملفات JavaScript، وافصل route واحد فقط بـ dynamic import. لو initial JS ما نزلش 20% على الأقل، ارجع للقياس بدل ما تكمل تقسيم عشوائي.

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة