برنامه های وب امروزی می توانند بسیار بزرگ شوند، به خصوص بخش جاوا اسکریپت آنها. از اواسط سال 2018، بایگانی HTTP اندازه متوسط انتقال جاوا اسکریپت را در دستگاه های تلفن همراه تقریباً 350 کیلوبایت قرار می دهد. و این فقط اندازه انتقال است! جاوا اسکریپت اغلب هنگام ارسال از طریق شبکه فشرده می شود، به این معنی که مقدار واقعی جاوا اسکریپت پس از اینکه مرورگر آن را از حالت فشرده خارج کرد، کمی بیشتر می شود. اشاره به این مهم است، زیرا تا آنجا که به پردازش منابع مربوط می شود، فشرده سازی بی ربط است. 900 کیلوبایت جاوا اسکریپت از حالت فشرده خارج شده هنوز 900 کیلوبایت برای تجزیه کننده و کامپایلر است، حتی اگر زمانی که فشرده شود ممکن است تقریباً 300 کیلوبایت باشد.
جاوا اسکریپت یک منبع گران قیمت برای پردازش است. بر خلاف تصاویری که پس از دانلود فقط زمان رمزگشایی نسبتاً کمی دارند، جاوا اسکریپت باید تجزیه، کامپایل و سپس در نهایت اجرا شود. بایت به بایت، این امر جاوا اسکریپت را نسبت به سایر انواع منابع گران تر می کند.
در حالی که به طور مداوم بهبودهایی برای بهبود کارایی موتورهای جاوا اسکریپت انجام می شود ، بهبود عملکرد جاوا اسکریپت - مثل همیشه - وظیفه ای برای توسعه دهندگان است.
برای این منظور، تکنیک هایی برای بهبود عملکرد جاوا اسکریپت وجود دارد. تقسیم کد ، یکی از این تکنیکها است که با پارتیشنبندی جاوا اسکریپت برنامه به تکهها، و ارائه آن تکهها به مسیرهای برنامهای که به آنها نیاز دارند، عملکرد را بهبود میبخشد.
در حالی که این تکنیک کار میکند، مشکل رایج برنامههای کاربردی سنگین جاوا اسکریپت، که شامل کدهایی است که هرگز استفاده نمیشوند را برطرف نمیکند. تکان دادن درخت برای حل این مشکل تلاش می کند.
تکان درخت چیست؟
تکان دادن درخت نوعی حذف کد مرده است. این اصطلاح توسط Rollup رایج شد ، اما مفهوم حذف کد مرده مدتی است که وجود داشته است. این مفهوم همچنین در وب پک خرید پیدا کرده است که در این مقاله از طریق یک برنامه نمونه نشان داده شده است.
اصطلاح "تکان درخت" از مدل ذهنی برنامه شما و وابستگی های آن به عنوان یک ساختار درخت مانند می آید. هر گره در درخت نشان دهنده یک وابستگی است که عملکرد مجزایی را برای برنامه شما فراهم می کند. در برنامههای مدرن، این وابستگیها از طریق عبارتهای import
ثابت مانند زیر وارد میشوند:
// Import all the array utilities!
import arrayUtils from "array-utils";
وقتی یک برنامه جوان است - اگر بخواهید یک نهال - ممکن است وابستگی کمی داشته باشد. همچنین از بیشتر – اگر نه همه – وابستگی هایی که اضافه می کنید استفاده می کند. با این حال، همانطور که برنامه شما بالغ می شود، وابستگی های بیشتری می توانند اضافه شوند. برای مسائل مرکب، وابستگیهای قدیمیتر از دسترس خارج میشوند، اما ممکن است از پایگاه کد شما حذف نشوند. نتیجه نهایی این است که یک برنامه در نهایت با جاوا اسکریپت استفاده نشده زیادی ارسال می شود. درخت تکان دادن با بهره گیری از نحوه کشش عبارات import
ایستا در بخش های خاصی از ماژول های ES6 این مشکل را برطرف می کند:
// Import only some of the utilities!
import { unique, implode, explode } from "array-utils";
تفاوت بین این نمونه import
و نمونه قبلی در این است که به جای وارد کردن همه چیز از ماژول "array-utils"
- که می تواند کد زیادی باشد) - این مثال فقط بخش های خاصی از آن را وارد می کند. در ساختهای توسعهدهنده، این چیزی را تغییر نمیدهد، زیرا کل ماژول بدون توجه به آن وارد میشود. در ساختهای تولیدی، وب پک میتواند به گونهای پیکربندی شود که صادرات ماژولهای ES6 را که بهصراحت وارد نشدهاند، از بین ببرد و این بیلدهای تولیدی را کوچکتر کند. در این راهنما، یاد خواهید گرفت که چگونه این کار را انجام دهید!
یافتن فرصت هایی برای تکان دادن درخت
برای اهداف توضیحی، یک نمونه برنامه یک صفحه ای موجود است که نشان می دهد تکان دادن درخت چگونه کار می کند. میتوانید آن را شبیهسازی کنید و در صورت تمایل دنبال کنید، اما ما در این راهنما همه مراحل را با هم پوشش خواهیم داد، بنابراین شبیهسازی ضروری نیست (مگر اینکه یادگیری عملی کار شما باشد).
برنامه نمونه یک پایگاه داده قابل جستجو از پدال های جلوه گیتار است. شما یک پرس و جو وارد می کنید و لیستی از پدال های افکت ظاهر می شود.
رفتاری که این برنامه را هدایت میکند به فروشنده (به عنوان مثال، Preact و Emotion ) و بستههای کد خاص برنامه (یا «تکهها»، همانطور که پک وب آنها را مینامد، تفکیک میشود:
بستههای جاوا اسکریپت که در شکل بالا نشان داده شدهاند، ساختهای تولیدی هستند، به این معنی که از طریق uglification بهینه شدهاند. 21.1 کیلوبایت برای یک بسته خاص برنامه بد نیست، اما باید توجه داشت که هیچ تکانی درختی رخ نمی دهد. بیایید به کد برنامه نگاه کنیم و ببینیم برای رفع آن چه کاری می توان انجام داد.
در هر برنامهای، یافتن فرصتهای تکان دادن درخت مستلزم جستجوی عبارات import
ثابت است. در نزدیکی بالای فایل کامپوننت اصلی ، خطی مانند این را خواهید دید:
import * as utils from "../../utils/utils";
میتوانید ماژولهای ES6 را به روشهای مختلفی وارد کنید ، اما مواردی مانند این باید توجه شما را جلب کنند. این خط خاص می گوید " همه چیز را از ماژول utils
import
و آن را در فضای نامی به نام utils
قرار دهید." سوال بزرگی که در اینجا می توان پرسید این است که "در آن ماژول چقدر چیز وجود دارد؟"
اگر به کد منبع ماژول utils
نگاه کنید، متوجه خواهید شد که حدود 1300 خط کد وجود دارد.
آیا به این همه چیز نیاز دارید؟ بیایید با جستجوی فایل مؤلفه اصلی که ماژول utils
را وارد میکند دوباره بررسی کنیم تا ببینیم چند نمونه از آن فضای نام ظاهر میشود.
همانطور که مشخص است، فضای نام utils
تنها در سه نقطه در برنامه ما ظاهر می شود - اما برای چه توابعی؟ اگر دوباره به فایل مؤلفه اصلی نگاهی بیندازید، به نظر میرسد که تنها یک تابع است که utils.simpleSort
است که برای مرتبسازی فهرست نتایج جستجو بر اساس تعدادی معیار هنگام تغییر فهرستهای کشویی مرتبسازی استفاده میشود:
if (this.state.sortBy === "model") {
// `simpleSort` gets used here...
json = utils.simpleSort(json, "model", this.state.sortOrder);
} else if (this.state.sortBy === "type") {
// ..and here...
json = utils.simpleSort(json, "type", this.state.sortOrder);
} else {
// ..and here.
json = utils.simpleSort(json, "manufacturer", this.state.sortOrder);
}
از یک فایل 1300 خطی با انبوه صادرات، فقط یکی از آنها استفاده می شود. این منجر به ارسال تعداد زیادی جاوا اسکریپت استفاده نشده می شود.
اگرچه این برنامه نمونه مسلماً کمی ساختگی است، اما این واقعیت را تغییر نمیدهد که این نوع سناریوی مصنوعی شبیه فرصتهای بهینهسازی واقعی است که ممکن است در یک برنامه وب تولیدی با آن مواجه شوید. اکنون که فرصتی را برای مفید بودن تکان دادن درخت شناسایی کرده اید، واقعاً چگونه انجام می شود؟
حفظ Babel از انتقال ماژول های ES6 به ماژول های CommonJS
بابل یک ابزار ضروری است، اما ممکن است مشاهده اثرات تکان دادن درختان را کمی دشوارتر کند. اگر از @babel/preset-env
استفاده میکنید، Babel ممکن است ماژولهای ES6 را به ماژولهای CommonJS سازگارتر تبدیل کند—یعنی ماژولهایی که به جای import
require
.
از آنجایی که تکان دادن درخت برای ماژولهای CommonJS دشوارتر است، وبپک نمیداند چه چیزی را از باندلها هرس کند اگر تصمیم به استفاده از آنها داشته باشید. راه حل این است که @babel/preset-env
را پیکربندی کنید تا به صراحت ماژول های ES6 را به حال خود رها کنید. هر جا Babel را پیکربندی کنید - چه در babel.config.js
یا package.json
- این شامل اضافه کردن یک چیز اضافی است:
// babel.config.js
export default {
presets: [
[
"@babel/preset-env", {
modules: false
}
]
]
}
مشخص کردن modules: false
در پیکربندی @babel/preset-env
شما باعث میشود Babel مطابق دلخواه رفتار کند، که به وبپک اجازه میدهد درخت وابستگی شما را تجزیه و تحلیل کند و وابستگیهای استفاده نشده را از بین ببرد.
در نظر گرفتن عوارض جانبی
یکی دیگر از جنبه هایی که باید در هنگام از بین بردن وابستگی ها از برنامه خود در نظر بگیرید این است که آیا ماژول های پروژه شما دارای عوارض جانبی هستند یا خیر. یک مثال از یک عارضه جانبی زمانی است که یک تابع چیزی خارج از محدوده خود را تغییر می دهد، که یکی از عوارض جانبی اجرای آن است:
let fruits = ["apple", "orange", "pear"];
console.log(fruits); // (3) ["apple", "orange", "pear"]
const addFruit = function(fruit) {
fruits.push(fruit);
};
addFruit("kiwi");
console.log(fruits); // (4) ["apple", "orange", "pear", "kiwi"]
در این مثال، addFruit
هنگامی که آرایه fruits
را اصلاح میکند، یک عارضه جانبی ایجاد میکند که خارج از محدوده آن است.
عوارض جانبی برای ماژولهای ES6 نیز اعمال میشود و این در زمینه تکان دادن درخت اهمیت دارد. ماژولهایی که ورودیهای قابل پیشبینی را دریافت میکنند و خروجیهای قابل پیشبینی یکسانی را بدون تغییر چیزی خارج از محدوده خود تولید میکنند، وابستگیهایی هستند که اگر از آنها استفاده نکنیم، میتوان با خیال راحت کنار گذاشت. آنها تکه های کد ماژولار و مستقلی هستند. از این رو، "ماژول".
در مورد وبپک، میتوان از یک اشاره برای تعیین اینکه بسته و وابستگیهای آن عاری از عوارض جانبی هستند، با مشخص کردن "sideEffects": false
در فایل package.json
پروژه استفاده کرد:
{
"name": "webpack-tree-shaking-example",
"version": "1.0.0",
"sideEffects": false
}
از طرف دیگر، میتوانید به وبپک بگویید که کدام فایلهای خاص بدون عوارض جانبی نیستند:
{
"name": "webpack-tree-shaking-example",
"version": "1.0.0",
"sideEffects": [
"./src/utils/utils.js"
]
}
در مثال اخیر، هر فایلی که مشخص نشده باشد، فاقد عوارض جانبی فرض می شود. اگر نمیخواهید این را به فایل package.json
خود اضافه کنید، میتوانید این پرچم را در پیکربندی بسته وب خود از طریق module.rules
نیز مشخص کنید .
واردات فقط موارد مورد نیاز
پس از دستور به Babel برای رها کردن ماژولهای ES6، یک تنظیم جزئی در نحو import
ما لازم است تا فقط توابع مورد نیاز ماژول utils
را وارد کنیم. در مثال این راهنما، تنها چیزی که نیاز است تابع simpleSort
است:
import { simpleSort } from "../../utils/utils";
از آنجا که فقط simpleSort
به جای کل ماژول utils
وارد می شود، هر نمونه از utils.simpleSort
باید به simpleSort
تغییر کند:
if (this.state.sortBy === "model") {
json = simpleSort(json, "model", this.state.sortOrder);
} else if (this.state.sortBy === "type") {
json = simpleSort(json, "type", this.state.sortOrder);
} else {
json = simpleSort(json, "manufacturer", this.state.sortOrder);
}
این باید تمام چیزی باشد که برای تکان دادن درخت در این مثال لازم است. این خروجی بسته وب قبل از تکان دادن درخت وابستگی است:
Asset Size Chunks Chunk Names
js/vendors.16262743.js 37.1 KiB 0 [emitted] vendors
js/main.797ebb8b.js 20.8 KiB 1 [emitted] main
این خروجی پس از موفقیت آمیز بودن تکان دادن درخت است:
Asset Size Chunks Chunk Names
js/vendors.45ce9b64.js 36.9 KiB 0 [emitted] vendors
js/main.559652be.js 8.46 KiB 1 [emitted] main
در حالی که هر دو بسته کوچک شدند، این در واقع بسته main
است که بیشترین سود را دارد. با تکان دادن قسمت های استفاده نشده ماژول utils
، بسته main
حدود 60٪ کوچک می شود. این نه تنها مدت زمان دانلود اسکریپت را کاهش می دهد، بلکه زمان پردازش را نیز کاهش می دهد.
برو چند درخت را تکان بده!
هر مسافت پیموده شده ای که از تکان دادن درختان بدست می آورید به برنامه شما و وابستگی ها و معماری آن بستگی دارد. آن را امتحان کنید! اگر به درستی میدانید که بستهکننده ماژول خود را برای انجام این بهینهسازی تنظیم نکردهاید، هیچ ضرری ندارد تلاش کنید و ببینید که چگونه برنامه شما به نفع آن است.
ممکن است از تکان دادن درختان به افزایش عملکرد قابل توجهی پی ببرید، یا اصلاً زیاد نباشد. اما با پیکربندی سیستم ساخت خود برای استفاده از این بهینهسازی در ساختهای تولیدی و وارد کردن انتخابی تنها آنچه برنامه شما نیاز دارد، به طور فعال بستههای برنامه خود را تا حد امکان کوچک نگه میدارید.
تشکر ویژه از کریستوفر باکستر، جیسون میلر ، آدی عثمانی ، جف پوسنیک ، سم ساکون و فیلیپ والتون برای بازخورد ارزشمندشان که کیفیت این مقاله را به طور قابل توجهی بهبود بخشید.