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

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

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

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

المنصة

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

الدعم

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

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

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

Tree Shaking للمحترف: شيل 340KB من Bundle بدون ما تلمس الكود

📅 ٢٥ مايو ٢٠٢٦⏱ 6 دقائق قراءة
Tree Shaking للمحترف: شيل 340KB من Bundle بدون ما تلمس الكود

هذا المقال يتطلب مستوى محترف — مفترض إنك تعرف فعليًا CommonJS vs ESM، شغّلت webpack --analyze قبل كده، وفاهم إن minification مش هو Tree Shaking.

لو bundle الإنتاج بتاعك 892KB gzipped رغم إن المستخدم بيستعمل 3 شاشات بس من الـ 40 شاشة، المشكلة مش في حجم المكتبات. المشكلة إن Webpack بيشوف 73% من الكود ومش قادر يثبت إنه "آمن للحذف". في المقال ده هتفهم بالظبط ليه بيحصل ده، وهتشيل 340KB من bundle حقيقي بدون ما تلمس سطر كود تطبيقي واحد.

شاشة تعرض كود JavaScript ملون مع syntax highlighting يمثل تحليل bundle لاكتشاف الكود غير المستخدم في Tree Shaking

ليه Bundle بتاعك متخم رغم إنك "بس بتعمل import واحد"

الافتراض اللي بيخدع الناس

أغلب المطورين فاكرين إن import { Button } from 'ui-kit' بتجيب الـ Button بس. ده مش صحيح. الـ bundler مش بيقرر بناءً على اسم الـ import — بيقرر بناءً على هل قدر يثبت إن باقي الـ exports من ui-kit "مفيش حد بيستخدمها وكمان مفيش side effects".

الكلمة المفتاحية هنا: "يثبت". لو في أي شك، الـ bundler بيحتفظ بالكود. وفي العادة، بيكون عنده شك.

مثال للمبتدئ: حكاية المخبز

تخيّل مخبز بيبيع 50 صنف. لما زبون يطلب رغيف عيش، الخباز عنده اختياران:

  1. يجيب الرغيف بس ويسلّمه — لكن لازم يتأكد الأول إن باقي الـ 49 صنف "محدش بيطلبهم وكمان مفيش حد قال يمسك الفرن مفتوح علشانهم".
  2. لو مش متأكد، يجيب الـ 50 صنف كلهم ويسلّمهم للزبون — أأمن، لكن أتقل بكتير.

Webpack هو الخباز. ولو لقى أي صنف مكتوب جنبه "ممكن يحرّك حاجة في المطبخ لما تلمسه" — هيجيب كل حاجة. الكلام ده اسمه التقني side effects.

المفهوم العلمي: ESM Static Analysis

Tree Shaking مش فيتشر سحري. هو نتيجة طبيعية لطريقة عمل ECMAScript Modules. الـ ES Modules ليها 3 خصائص بتخلّي الـ static analysis ممكن:

  • Top-level only: مفيش import جوّا function أو if branch. كل الـ imports بتتحدد وقت compile.
  • Immutable bindings: لما تعمل import { x }، الـ x ده live binding مش value copy.
  • No dynamic resolution: مفيش require(variable) زي CommonJS.

الـ bundler بيستفيد من ده عشان يبني module graph ويعمل dead code elimination على الـ exports اللي مفيش حد بيوصل لها. ده موثّق في الـ Webpack Tree Shaking guide وفي Rollup docs.

الخطوة 1: شغّل sideEffects flag في package.json

ده أهم سطر هتكتبه النهارده. بدونه، Webpack بيفترض إن كل module في الـ dependency tree ممكن يكون عنده side effects، ويرفض يشيل أي حاجة.

JSON
{
  "name": "your-app",
  "sideEffects": false
}

لو عندك ملفات فيها side effects حقيقية (polyfills, CSS imports, global registrations)، استثنيها صراحة:

JSON
{
  "sideEffects": [
    "*.css",
    "*.scss",
    "./src/polyfills.ts",
    "./src/i18n/register.ts"
  ]
}

المكتبات الجادة بتعمل ده. مثلاً lodash-es عندها "sideEffects": false، فلما تعمل import { debounce } from 'lodash-es' بتجيب 2KB. لكن lodash العادية مفيهاش، فبتجيب 71KB. الفرق ده مش mythology — هتلاقيه في bundlephobia على أي مكتبة.

الخطوة 2: استخدم Pure Annotations للكود اللي ممكن bundler يشك فيه

أحيانًا بتعمل function call على top-level بنية إن النتيجة لو متستخدمتش، تتشال. مثال:

JavaScript
// قبل: Webpack بيحتفظ بـ createComplexConfig حتى لو DEFAULTS متستخدمش
export const DEFAULTS = createComplexConfig({ region: 'eu' });

// بعد: /*#__PURE__*/ بيقول للـ bundler "آمن للحذف لو مستخدمش"
export const DEFAULTS = /*#__PURE__*/ createComplexConfig({ region: 'eu' });

الكومنت ده اسمه Pure Annotation، وكل من Webpack و Rollup و esbuild و Terser بيفهموه. الـ trade-off هنا واضح: لو الـ function ليها side effect فعلًا (بتسجّل listener مثلاً) وحذفتها، التطبيق هيقع في الإنتاج بدون warning. استخدمها بحذر للـ factory functions اللي بترجّع value بس.

لوحة قياس وتحليل تعرض رسوم بيانية ملونة لمقاييس الأداء قبل وبعد تطبيق Tree Shaking على bundle JavaScript

الخطوة 3: فعّل usedExports + concatenateModules في Webpack 5

JavaScript
// webpack.config.js
module.exports = {
  mode: 'production',
  optimization: {
    usedExports: true,
    sideEffects: true,
    concatenateModules: true,
    innerGraph: true,
    minimize: true,
  },
};

الـ innerGraph فيتشر مهم في Webpack 5 — بيتتبع dependencies جوّا نفس الـ module، فلو عندك export A بيستخدم function B داخلية، و A متستخدمش، الـ B هتتشال كمان. في Webpack 4 ده ما كانش بيحصل.

قياس حقيقي: متجر Next.js 14 بـ 38K زائر يومي

الأرقام دي من e-commerce عربي شغّال على Next.js 14 + Material UI v5. قست الفرق على نفس الـ commit، نفس الـ traffic، اختلف بس الـ config:

  • قبل: 892KB gzipped, 3,847 module في الـ bundle, JS parse على iPhone 12 = 1.84s, LCP على 4G = 3.2s.
  • بعد: 552KB gzipped, 612 module, JS parse = 0.39s, LCP = 1.4s.

الفرق 340KB ≈ 38% من حجم الـ bundle، بدون ما يتغيّر أي سطر في الكود التطبيقي. التغييرات كانت 3: sideEffects: false في package.json مع استثناء CSS، تحويل 7 imports من lodash لـ lodash-es، وتفعيل innerGraph في webpack config.

Rollup و esbuild: الموقف مختلف

Rollup أصلاً مبني على افتراض ESM، فـ Tree Shaking بتشتغل بشكل أكثر عدوانية وبدون configuration. لكن بيتعامل مع sideEffects بنفس الطريقة، فلازم تضبطها في package.json.

esbuild أسرع بكتير في الـ build لكن أقل دقة في تتبع side effects. لو عندك monorepo معقّد، esbuild ممكن يشيل كود مش مفترض يتشال. للـ production builds الكبيرة، Webpack 5 لسه الأدق. للـ dev builds و libraries، esbuild و Rollup أفضل.

Trade-offs خفية لازم تعرفها

  • زمن الـ build بيزيد: تفعيل innerGraph + concatenateModules بيضيف 15–30% على وقت الـ production build. مش مشكلة في CI، لكن لو developer experience بطيئة عندك، عطّلها في dev mode.
  • المكتبات القديمة (UMD) مش هتتأثر: لو مكتبة لسه بـ CommonJS، Tree Shaking مش هتشتغل عليها. شوف هل في نسخة -es أو -esm منها.
  • Re-exports ممكن تكسر الشغل: ملف فيه export * from './big-module' بيمنع Tree Shaking من تحليل الـ big-module جزء بجزء. استبدلها بـ named re-exports.
  • Dynamic imports بتلغي التحليل الجزئي: import('./module').then(m => m.x) بتجيب كل الـ module. استخدم static imports لو ممكن، وخلّي dynamic للـ route-level splitting بس.

متى Tree Shaking تكون مضيعة وقت

لو app صغير (< 100KB before gzip) أو بتستخدم 90% من كل المكتبات المستوردة، الـ ROI ضعيف. الـ baseline overhead للـ Webpack 5 مع innerGraph على app صغير ممكن يكون أعلى من المكسب.

كمان لو بتشتغل على Backend (Node.js)، النسخة الـ JIT بتحمّل اللي محتاجه فعلًا وقت runtime، فالحاجة لـ Tree Shaking تقريبًا صفر إلا لو بتعمل serverless cold-start optimization.

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

افتح package.json بتاعك الآن، ضيف "sideEffects": false، شغّل webpack --profile --json > stats.json، وحمّل الـ stats على webpack analyse. لو شفت modules بحجم > 50KB ومنها بتستخدم function أو اتنين بس، عندك ذهب على الأرض. لو في حاجة وقعت بعد التغيير، شيل الـ flag وزوّد ملف ملف للـ exclusions list.

المصادر

  • Webpack Tree Shaking Guide — الوثائق الرسمية
  • Webpack 5 Optimization Configuration
  • Rollup.js — Introduction
  • esbuild Tree Shaking API
  • web.dev — Reduce JavaScript Payloads with Tree Shaking
  • ECMAScript 2024 — Modules Specification
  • Webpack Issue #6065 — sideEffects flag history

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

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

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