Tree Shaking — آلية إزالة الكود غير المستخدم (dead code elimination) في مرحلة بناء التطبيق. يقوم Tree Shaking بتحليل الهيكل الثابت لوحدات ES ويستبعد الدوال والفئات والمتغيرات المُصدرة التي لا يتم استيرادها في أي مكان. وفقاً لوثائق Webpack، يمكن للتكوين الصحيح لـ Tree Shaking تقليل حجم الحزمة بنسبة 30–60% دون تغيير وظائف التطبيق.
أهم النقاط
Tree Shaking — تقنية تحسين الكود تستبعد الوحدات والدوال غير المستخدمة من الحزمة النهائية. تم تقديم المصطلح من قبل فريق Rollup في 2015 ويصف العملية مجازياً: يتم هز شجرة التبعيات وتسقط الفروع غير المستخدمة. على عكس التحسين اليدوي، يتم تنفيذ Tree Shaking تلقائياً في مرحلة البناء.
يعمل Tree Shaking فقط مع وحدات ES (ECMAScript Modules)، حيث يتم تحديد التبعيات بشكل ثابت من خلال import و export. لا يدعم CommonJS (require/module.exports) Tree Shaking لأن require يتم تنفيذه ديناميكياً — لا يستطيع المجمّع تحديد الدوال المستخدمة فعلياً مسبقاً. المكتبات الحديثة (Lodash، Moment.js، RxJS) تصدر إصدارات ES لدعم Tree Shaking.
Rollup — أول مجمّع يطبق Tree Shaking في 2015. على عكس Webpack، صُمم Rollup من البداية لوحدات ES ويقوم بإزالة أكثر عدوانية للكود الميت. يحلل Rollup ليس فقط الصادرات الفردية بل الوحدات بأكملها: إذا لم يكن للوحدة آثار جانبية ولم يتم استخدام أي من صادراتها، يستبعد Rollup الوحدة بأكملها من الحزمة.
Rollup فعال بشكل خاص للمكتبات و SDKs حيث كل كيلوبايت له أهمية. إطار العمل Vue.js يستخدم Rollup لبناء نسخة الإنتاج. انتقل React إلى Rollup في 2020. بالنسبة للتطبيقات، يُستخدم Webpack بشكل أكثر شيوعاً بسبب نظامه الغني بالإضافات (Hot Module Replacement، code splitting، CSS modules)، ولكن لتحقيق أقصى Tree Shaking عند بناء المكتبات، يظل Rollup هو المعيار الصناعي.
التوفير من Tree Shaking يعتمد بشكل كبير على هندسة المشروع. في تطبيق React باستخدام مكتبة Ant Design، يمكن لـ Tree Shaking إزالة ما يصل إلى 70% من كود مكونات الواجهة. في مشروع حيث جميع الاستيرادات محددة ومباشرة، سيكون التوفير 5–15%. وفقاً لأبحاث Webpack، متوسط التوفير هو 30–40% من حجم الحزمة.
| نوع الكود الميت | مثال | كشف Tree Shaking |
|---|---|---|
| تصدير غير مستخدم | export function unusedHelper() | نعم |
| استيراد غير مستخدم | import { unused } from "lib" | نعم |
| فرع شرط ميت | if (false) { ... } | لا (يزاله المُصغّر) |
| دالة غير مستدعاة بعد DCE | function a(){} a() حيث a غير مستدعاة | جزئياً |
آلية Tree Shaking تعتمد على رسم بياني للتبعيات يقوم المجمّع ببنائه من جميع تعليمات import/export في المشروع. في المرحلة الأولى، يجتاز المجمّع جميع الملفات من نقطة الدخول ويجمع شجرة الوحدات. في المرحلة الثانية، يحلل أي الصادرات من كل وحدة يتم استيرادها فعلياً في الوحدات الأخرى.
لكل وحدة، Webpack أو Rollup يضع علامة على الصادرات كمستخدمة أو غير مستخدمة. يتم استبعاد الصادرات غير المستخدمة من الحزمة. ومع ذلك، تبقى الوحدة نفسها في الحزمة إذا تم استخدام أحد صادراتها على الأقل. يمكن استبعاد الوحدة بالكامل فقط من خلال علامة sideEffects أو إذا كانت الوحدة لا تحتوي على أي آثار جانبية.
// utils.js — وحدة بها دوال
export function formatDate(date) {
return date.toISOString().slice(0, 10);
}
export function formatCurrency(amount) {
return "$" + amount.toFixed(2);
}
export function slugify(text) {
return text.toLowerCase().replace(/\s+/g, "-");
}// app.js — نقطة الدخول
import { formatDate } from "./utils";
const today = formatDate(new Date());
console.log(today);// بعد Tree Shaking — في الحزمة فقط formatDate
function formatDate(date) {
return date.toISOString().slice(0, 10);
}
const today = formatDate(new Date());
console.log(today);Tree Shaking استبعد formatCurrency و slugify من الحزمة النهائية لأنهما غير مستوردين في app.js. انخفض حجم وحدة utils.js من 3 دوال إلى 1. إذا كانت utils.js تحتوي على آثار جانبية (مثل التهيئة العامة)، فلن يتمكن Tree Shaking من إزالة حتى الصادرات غير المستخدمة.
Webpack يتضمن دعماً مدمجاً لـ Tree Shaking من خلال TerserPlugin في وضع الإنتاج. لتفعيل Tree Shaking، شرطان كافيان: mode مضبوط على production (mode: "production") والوحدات تستخدم صياغة ES (import/export). يقوم Webpack تلقائياً بوضع علامة على الصادرات غير المستخدمة ويمررها إلى Terser للإزالة.
التكوين الإضافي usedExports: true في optimization.webpack.config.js يتيح تحليلاً تفصيلياً لاستخدام الصادرات داخل الوحدة. هذا الخيار يحدد أي الصادرات تُستخدم فعلياً وأيها مُصدرة فقط (provided). مزيج usedExports و Terser يعطي أقصى كفاءة لإزالة الكود الميت.
// webpack.config.js — تكوين Tree Shaking
module.exports = {
mode: "production",
entry: "./src/app.js",
output: {
filename: "bundle.js",
},
optimization: {
usedExports: true,
minimize: true,
concatenateModules: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules\/(?!(my-lib)\/).*/,
use: {
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", { modules: false }],
],
},
},
},
],
},
};المعامل الرئيسي هو modules: false في @babel/preset-env. افتراضياً، يقوم Babel بتحويل وحدات ES إلى CommonJS، مما يقتل Tree Shaking. modules: false يمنع Babel من تحويل import/export، محافظاً على صياغة ES لـ Webpack. concatenateModules أيضاً يدمج الوحدات في نطاق مشترك، مما يقلل عدد IIFE ويخفض حجم الحزمة.
الآثار الجانبية (Side effects) — إجراءات تقوم بها الوحدة عند استيرادها ولا تتعلق بالقيم المُصدرة: الأنماط العامة (import "./styles.css")، polyfills (import "core-js/stable")، تهيئة المتغيرات العامة أو تسجيل Service Worker. إذا كانت الوحدة تحتوي على آثار جانبية، لا يمكن للمجمّع إزالتها بأمان من الحزمة حتى لو لم يتم استخدام أي من صادراتها.
علامة sideEffects في package.json تخبر المجمّع بالوحدات التي لا تحتوي على آثار جانبية في الحزمة. للحزمة حيث جميع الوحدات نقية (تصدير دوال فقط)، يجب تحديد "sideEffects": false. للحزم التي تحتوي على CSS أو polyfills — مصفوفة من المسارات للملفات ذات الآثار الجانبية: "sideEffects": ["*.css"]. بدون هذه العلامة، لن يزيل Tree Shaking حتى الدوال غير المستخدمة.
للتحقق مما إذا كانت الوحدة تحتوي على آثار جانبية، اسأل نفسك: هل سيقوم هذا الاستيراد بأي إجراءات لا تتعلق بتصدير القيم؟ import "./styles.css" يضيف CSS إلى DOM — هذا أثر جانبي. import { throttle } from "lodash-es" ليس له آثار جانبية —他只是 يجعل دالة throttle متاحة. polyfills (import "core-js/stable") لها آثار جانبية — تُعدل النماذج الأولية العامة.
لوحداتك الخاصة، يُوصى بـ: استخراج الأنماط و polyfills إلى نقاط دخول منفصلة، فصل الأدوات النقية (دوال بدون آثار جانبية) عن الوحدات ذات الآثار الجانبية (التهيئة، التسجيل، تسجيل Service Worker). في package.json للمشروع العلوي، حدد "sideEffects": false فقط إذا كانت جميع الوحدات نقية. إذا كان هناك أنماط، حدد "sideEffects": ["*.css"] بدقة.
{
"name": "my-ui-lib",
"version": "2.1.0",
"sideEffects": [
"*.css",
"polyfills.js"
],
"module": "dist/index.esm.js",
"main": "dist/index.cjs.js"
}"sideEffects": ["*.css", "polyfills.js"] يعني: جميع ملفات CSS لها آثار جانبية (لا يمكن إزالتها) وكذلك polyfills.js. جميع ملفات JS الأخرى في الحزمة نقية — يمكن هزها بأمان. حقل module يحدد المسار إلى إصدار ES من الحزمة الذي يجب على المجمّع استخدامه بدلاً من إصدار CommonJS (main) لـ Tree Shaking.
React Native مع Metro Bundler يدعم نسخة محدودة من Tree Shaking. Metro لا يقوم بتحليل ثابت كامل للصادرات المستخدمة (usedExports) مثل Webpack. بدلاً من ذلك، يعتمد Metro على Terser لإزالة الأجزاء غير المستخدمة من الوحدات أثناء التصغير. فعالية هذا النهج أقل من Tree Shaking الكامل في Webpack.
للتحسين الأقصى لمشاريع React Native، يُوصى بـ: استخدام مكتبات بوحدات ES (حقل module في package.json)، إضافة babel-plugin-transform-remove-console لإزالة كود التصحيح، وتكوين Metro transformer.minifierConfig لـ Terser. بالإضافة إلى ذلك، Ram Bundle (تقسيم الحزمة إلى وحدات) يقلل تحميل الشاشات غير المستخدمة.
الأسئلة الشائعة
CommonJS (require/module.exports) لا يدعم التحليل الثابت — يمكن استدعاء require ديناميكياً داخل الشروط والدوال. لا يستطيع المجمّع تحديد أي أجزاء الوحدة تُستخدم فعلياً. فقط وحدات ES مع import/export الثابت تسمح بـ Tree Shaking.
TypeScript متوافق تماماً مع Tree Shaking بشرط أن يكون tsconfig.json مهيأً لوحدات ES: "module": "esnext". يجب على مترجم TypeScript الحفاظ على import/export دون تحويلها إلى CommonJS. Babel مع @babel/preset-typescript و modules: false يمرر أيضاً وحدات ES بشكل صحيح إلى Webpack.
Webpack Bundle Analyzer — إضافة تُصور تركيبة الحزمة كرسم بياني تفاعلي. إذا كانت مكتبة موجودة في الحزمة ولكن دوالها غير مستخدمة، فإن Tree Shaking لم يعمل. يمكنك أيضاً تحليل ملف الإخراج: ابحث عن الصادرات غير المستخدمة في نص الحزمة عبر grep.
Lodash v4 يُوزع كحزمة CommonJS. لـ Tree Shaking، تحتاج إلى استخدام lodash-es — إصدار ES من المكتبة. استبدل import throttle from "lodash/throttle" بـ import { throttle } from "lodash-es" وكون figure resolve.alias في Webpack لاستبدال lodash بـ lodash-es.
Tree Shaking يزيد وقت البناء قليلاً (بنسبة 5–15%) لأنه يضيف مرحلة تحليل الرسم البياني للتبعيات ووضع علامات على الصادرات المستخدمة. في وضع التطوير، عادة ما يكون Tree Shaking معطلاً للسرعة. في الإنتاج، الوقت الإضافي مبرر بتقليل كبير في حجم الحزمة.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا