آیا چیزی از Tree Shaking می‌دانید؟

Tree Shaking چیست؟

ابتدا باید در مورد webpack توضیح بدهم: webpack یک module bundler است. توسعه دهندگان جاوا اسکریپت با مشکل بزرگی روبرو هستند و آن هم تعداد بالای فایل های جاوا اسکریپت و به طور کل asset های وب سایت ها است که باعث کندی سرعت بارگذاری سایت می شوند. عملیات باندل کردن (bundling) تمام این فایل ها را با هم ادغام می کند تا برای دریافت آن ها فقط یک درخواست به سمت سرور ارسال شود. کار webpack نیز همین است؛ یعنی فایل ها را باندل کرده و به طور هوشمند و با در نظر گرفتن اولویت ها ارسال می کند.

tree shaking

Tree Shaking (به معنی «درخت تکانی») یک استراتژی است که توسعه دهندگان وب از آن برای کوچک کردن کدهای جاوا اسکریپت و حذف کد های اضافی استفاده می کنند.

در این مقاله ی آموزشی می خواهیم نحوه ی انجام tree shaking در جاوا اسکریپت را با استفاده از webpack توضیح دهیم. در واقع Tree Shaking که به نام live code inclusion هم شناخته می شود روشی برای بهینه سازی کدهای جاوا اسکریپتی است که در قالب ECMAScript نوشته شده باشند.

با گذر زمان برنامه های وب ما دارای dependency (وابستگی به منابع یا کدهای خاص) می شوند اما برخی اوقات و پس از گذشت مدتی این وابستگی ها از بین می روند. نتیجه این می شود که کدهایی داریم که اصلا به آن ها نیازی نداریم! بنابراین هدف ما این است که این کدهای اضافی و به درد نخور را حذف کنیم تا باعث درگیری منابع سیستمی نشوند و سرعت برنامه ی وب ما را بالاتر ببرند. حالا متوجه شدید چرا به این تکنیک «درخت تکانی» می گویند؟ اگر کدهای جاوا اسکریپت خود را به صورت یک درخت تصور کنید، شاخ و برگ های مُرده می شود همان کدهای اضافی شما!

پیدا کردن کدهای مُرده، یعنی کدهایی که استفاده ای در برنامه ندارند، برای زبان های پویا کار دشواری بود اما ماژول های ECMAScript 6 از نوع استاتیک (پایا) هستند. بنابراین کار پیدا کردن این کدهای مرده تنها با import و export کردن انجام می شود. این مفهوم برای اولین بار توسط یک bundler به نام Rollup در سال 2015 معرفی شد اما امروزه اکثر bundler ها مانند webpack از آن پشتیبانی می کنند؛ webpack از نسخه ی 2 از tree shaking و ماژول های ECMAScript 6 پشتیبانی می کرده است اما در نسخه ی چهارم خود ویژگی های جالب و خوبی برای ارتقاء این عملیات اضافه کرده است.

چرا باید Tree Shaking انجام دهیم؟

از منظر پردازش و رندر، جاوا اسکریپت از دیگر منابع دنیای وب (مانند تصاویر و HTML و...) سنگین تر و پردازش آن پر هزینه تر است چرا که قبل از اجرا باید parse و سپس کامپایل شود! به همین دلیل پیشنهاد می شود تمام توسعه دهندگان وب قبل از باندل کردن کدهای جاوا اسکریپت خود، آن ها را تا حد ممکن کاهش دهند. بگذارید یک مثال برایتان بزنم؛ تصویر زیر مقایسه ی زمان لازم برای بارگذاری یک فایل جاوا اسکریپت به حجم 170KB و یک فایل تصویری JPEG به حجم 170KB است:

مقایسه ی زمان لازم برای بارگذاری یک فایل جاوااسکریپت و یک فایل JPEG با حجم یکسان - منبع: گوگل
مقایسه ی زمان لازم برای بارگذاری یک فایل جاوا اسکریپت و یک فایل JPEG با حجم یکسان - منبع: گوگل

می بینید که جاوا اسکریپت زمان بیشتری نیازد دارد!

نحوه ی عملکرد Tree Shaking

Tree Shaking در جاوا اسکریپت از دستورات استاتیک import استفاده می کند تا تنها ماژول هایی که موردنیاز شما است را در باندل قرار بدهد؛ در حالت developer build (هنگام توسعه) تمام موارد یک ماژول import خواهند شد اما در production build (اجرایی شدن وب سایت) می توانیم به webpack بگوییم چه چیزهایی را نیاز داریم. بدین صورت باندل جاوا اسکریپت ما کوچک تر و بهینه تر خواهد بود و طبیعتا سرعت بیشتری نیز خواهد داشت.

در برنامه های جاوا اسکریپتی امروزی وابستگی ها (dependency) از طریق دستورات استاتیک مانند دستور زیر import می شوند:

import arrayMenu from "array-menu";

کد بالا به webpack می گوید هر چه در ماژولی به نام array-menu وجود دارد را import کن اما اگر برنامه ی شما تنها از بخشی از یک پکیج استفاده می کند نیازی به ارسال تمام آن به سمت کاربر نیست. در این صورت می توانیم بگوییم:

import { burger, fries, shake } from "array-menu";

متاسفانه در حال حاضر هیچ فرآیندی برای خودکارسازی tree shaking وجود ندارد. بنابراین باید خودتان در کدها به دنبال فرصت استفاده از آن بوده و سپس به صورت دستی کدهایش را بنویسید؛ به طور مثال در کدهایتان به دنبال دستورات استاتیک import بگردید و سعی کنید توابع و کلاس های اضافی در کدهایتان را حذف کنید. سپس فایل component خود را طوری ویرایش کنید که تنها آنچه را نیاز دارید import کند.

یک نکته کوچک

اگر شما از کامپایلرهای جاوا اسکریپت مانند Babel استفاده می کنید، باید قبل از tree shaking چند کار را انجام بدهید؛ برخی از preset ها مانند babel-preset-env به طور خودکار ماژول های ES6 را به ماژول های CommonJS تبدیل می کنند که باعث پیچیدگی tree shaking می شود. راه حل این است که preset خود را طوری تنظیم کنید که ماژول های ES6 را دستکاری نکند. برای انجام این کار کافی است کد زیر را به فایل configuration (پیکربندی) Babel اضافه کنید:

{
    "presets": [
        ["env", {
            "modules": false
        }]
    ]
}

پس از آن bundler شما می تواند وابستگی های کدتان را آنالیز کند و به شما کمک کند که کدهای مُرده را از بین ببرید. همچنین برای بهره وری کامل از Tree shaking بهتر است کدهایتان را minify کنید؛ برای انجام این کار می توانید از پلاگین UglifyJS Plugin استفاده کنید. در چنین حالتی webpack کدهای مُرده را شناسایی و نشانه گذاری کرده و UglifyJS نیز کدها را تمیز کرده و کدهای مُرده را از باندل حذف می کند.

مثالی از Tree Shaking

در اینجا یک مثال webpack آورده ایم تا با عملیات Tree Shaking آشنا شوید. فرض کنید که فایل modules.js را بدین صورت داریم:

export function drive(props) {
    return props.gas
}

export function fly(props) {
    return props.miles
}

سپس فایل index.js را نیز با محتویات زیر داریم:

import { drive } from modules;

eventHandler = (event) => {
    event.preventDefault()
    drive({ gas: event.target.value })
}

در این مثال ()fly اهمیتی ندارد بنابراین وارد باندل هم نخواهد شد چرا که tree shaking آن را به عنوان کد مرده علامت گذاری کرده و UglifyJS آن را حذف می کند. در واقع webpack کلاس های استفاده شده را با پیام (harmony export (immutable و کلاس های بدون استفاده را با پیام unused harmony export مشخص می کند و برای آنکه UglifyJS متوجه این پیام ها شود باید حتما optimization.usedExports را روشن کرده باشیم.

با اینکه Tree Shaking تنها چند خط از کد بالا را حذف کرد اما در کل پروژه تاثیر زیادی دارد.

نکات تکمیلی Tree Shaking در جاوا اسکریپت

  • اگر tree shaking هیچ تاثیری روی کتابخانه های شما ندارد مطمئن شوید که متدهای آن بر اساس ES6 (و نه CommonJS) صادر (export) می شود. البته پلاگین هایی مانند webpack-common-shake برای CommonJS ساخته شده اند اما بهتر است از آن ها دوری کنید.
  • اگر از Lodash استفاده می کنید باید babel-plugin-lodash را به configuration (پیکربندی) Babel اضافه کنید تا دستور عادی import کار کند.
  • برخی از عوارض جانبی استفاده از tree shaking (مانند ویرایش برخی از اشیاء خارج از scope تابع) این فرآیند را پیچیده می کنند. برای شناسایی این عوارض و جلوگیری از وقوع آن ها می توانید از eslint-plugin-tree-shaking استفاده کنید.
  • پیدا کردن کدهای مُرده در برنامه هایی که ماژول های متعددی دارند کار سخت تری است اما خوشبختانه ابزارهایی مانند webpack-bundle-analyzer تحلیل این کدها را برای شما آسان تر می کنند.
  • ممکن است tree shaking در جاوا اسکریپت در ارتقاء سرعت برنامه ی شما تاثیر آنچنانی نداشته باشد. در این صورت دلسرد نشوید؛ ممکن است برنامه ی شما بسیار کوچک باشد و کدهای مرده تان نیز واقعا کم باشند. برای بهره گیری کامل از tree shaking بهتر است آن را با انواع روش های دیگر مانند minification و splitting ترکیب کنید.
نویسنده شوید

دیدگاه‌های شما (2 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

arash milani
27 فروردین 1400
عالی بود

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

فرشید رضایی
03 آبان 1399
بسیار مفید و ساده. ممنون

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.