Tree Shaking — مکانیزم حذف کد استفادهنشده (dead code elimination) در مرحله ساخت برنامه. Tree Shaking ساختار استاتیک ماژولهای ES را تحلیل میکند و توابع، کلاسها و متغیرهای صادر شدهای که در هیچ جا وارد نشدهاند را حذف میکند. طبق Webpack Documentation، پیکربندی صحیح 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 به ویژه برای کتابخانهها و SDKها که هر کیلوبایت اهمیت دارد مؤثر است. فریمورک Vue.js از Rollup برای ساخت نسخه production استفاده میکند. React در سال 2020 به Rollup مهاجرت کرد. برای برنامهها معمولاً از Webpack استفاده میشود (به دلیل اکوسیستم غنیتر پلاگینها — Hot Module Replacement, code splitting, CSS modules)، اما برای حداکثر Tree Shaking در ساخت کتابخانهها، Rollup استاندارد صنعت باقی مانده است.
صرفهجویی حاصل از Tree Shaking به شدت به معماری پروژه بستگی دارد. در یک برنامه React با کتابخانه Ant Design، Tree Shaking میتواند تا 70% کد کامپوننتهای UI را حذف کند. در پروژهای که همه importها خاص و نقطهای هستند، صرفهجویی 5–15% خواهد بود. میانگین صرفهجویی بر اساس تحقیقات Webpack 30–40% اندازه باندل است.
| نوع کد مرده | مثال | تشخیص توسط Tree Shaking |
|---|---|---|
| صادرات استفادهنشده | export function unusedHelper() | بله |
| import استفادهنشده | import { unused } from "lib" | بله |
| شاخه مرده شرط | if (false) { ... } | خیر (توسط مینیفایر حذف میشود) |
| تابع فراخوانینشده پس از DCE | function a(){} a() وقتی a فراخوانی نشود | جزئی |
مکانیزم Tree Shaking مبتنی بر گراف وابستگی (dependency graph) است که بیلدر از همه import/exportهای پروژه میسازد. در مرحله اول، بیلدر همه فایلها را از نقطه ورودی (entry point) پیمایش کرده و درخت ماژولها را جمعآوری میکند. در مرحله دوم، تحلیل میشود کدام صادرات از هر ماژول واقعاً در ماژولهای دیگر وارد شدهاند.
برای هر ماژول 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 شامل side effects باشد (مثلاً مقداردهی اولیه سراسری)، Tree Shaking نمیتواند حتی صادرات استفادهنشده را حذف کند.
Webpack شامل پشتیبانی داخلی Tree Shaking از طریق پلاگین TerserPlugin در حالت production است. برای فعالسازی Tree Shaking دو شرط کافی است: mode تنظیم شده به production (mode: "production") و ماژولها از نحو ES (import/export) استفاده کنند. Webpack به طور خودکار صادرات استفادهنشده را علامتگذاری کرده و برای حذف به Terser ارسال میکند.
پیکربندی اضافی usedExports: true در optimization.webpack.config.js تحلیل دقیق استفاده از صادرات درون ماژول را فعال میکند. این گزینه تعیین میکند کدام صادرات واقعاً استفاده میشوند (used) و کدام فقط صادر شدهاند (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")، polyfillها (import "core-js/stable")، مقداردهی اولیه متغیرهای سراسری یا ثبت Service Worker. اگر ماژول دارای side effects باشد، بیلدر نمیتواند آن را به طور ایمن از باندل حذف کند، حتی اگر هیچ صادراتی استفاده نشود.
پرچم sideEffects در package.json به بیلدر اطلاع میدهد کدام ماژولها در بسته اثر جانبی ندارند. برای بستهای که همه ماژولها خالص هستند (فقط صادرات توابع)، باید "sideEffects": false تنظیم شود. برای بستههای دارای CSS یا polyfillها — آرایهای از مسیرهای فایلهای با اثرات جانبی: "sideEffects": ["*.css"]. بدون این پرچم، Tree Shaking حتی توابع استفادهنشده را حذف نخواهد کرد.
برای بررسی اینکه آیا ماژولی اثر جانبی دارد، این سؤال را بپرسید: آیا این import عملی غیرمرتبط با صادرات مقادیر انجام میدهد؟ import "./styles.css" CSS را به DOM اضافه میکند — این یک side effect است. import { throttle } from "lodash-es" side effect ندارد — فقط تابع throttle را در دسترس قرار میدهد. Polyfillها (import "core-js/stable") side effect دارند — آنها پروتوتایپهای سراسری را تغییر میدهند.
برای ماژولهای خود توصیه میشود: استایلها و polyfillها را به نقاط ورودی جداگانه منتقل کنید، ابزارهای خالص (توابع بدون side effects) را از ماژولهای دارای اثرات جانبی (مقداردهی اولیه، لاگگیری، ثبت 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 دیگر در بسته خالص هستند — میتوان آنها را با خیال راحت shake کرد. فیلد module مسیر نسخه ES بسته را نشان میدهد که بیلدر باید به جای نسخه CommonJS (main) برای Tree Shaking استفاده کند.
React Native با Metro Bundler از نسخه محدودی از Tree Shaking پشتیبانی میکند. Metro مانند Webpack تحلیل استاتیک کامل صادرات استفادهشده (usedExports) را انجام نمیدهد. در عوض 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" جایگزین کنید و resolve.alias را در Webpack برای جایگزینی lodash با lodash-es پیکربندی کنید.
Tree Shaking زمان ساخت را اندکی افزایش میدهد (5–15%)، زیرا مرحله تحلیل گراف وابستگی و علامتگذاری صادرات استفادهشده را اضافه میکند. در حالت development، Tree Shaking معمولاً برای سرعت غیرفعال است. در production زمان اضافی با کاهش قابل توجه اندازه باندل توجیه میشود.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید