توسعه فرانت‌اند

Performance Budget: محدودیت حجم و سرعت

تعیین بودجه با محدوده و واحد روشن، تفاوت lab و field، Core Web Vitals و نمونه کامل Node برای بررسی مجموع gzip فایل‌های build.

1405/01/09 2 دقیقه مطالعه 849 بازدید

برای اضافه کردن کد، یک مرز قابل گفتگو لازم است

تیم می‌خواهد نمودار تازه‌ای به صفحهٔ گزارش اضافه کند. کتابخانهٔ پیشنهادی امکانات خوبی دارد، اما فایل ورودی صفحه را بزرگ‌تر می‌کند. بدون معیار، گفتگو به سلیقه محدود می‌شود: یکی ظاهر را می‌خواهد و دیگری نگران سرعت است. Performance Budget مجموعه‌ای از محدودیت‌های مشخص است که اثر تغییر را قابل بررسی می‌کند. این محدودیت می‌تواند اندازهٔ فایل، زمان یک رویداد یا تعداد منبع باشد. هدف، تصمیم روشن دربارهٔ هزینه و فایدهٔ تغییر است؛ هیچ budget به تنهایی تضمین نمی‌کند سایت همیشه سریع بماند.

یک سقف ثابت مثل ۲۰۰ کیلوبایت JavaScript برای تمام سایت‌ها، قانون عمومی نیست. صفحهٔ متن، پنل تحلیل و ابزار ویرایش نیازهای متفاوت دارند. مخاطب، دستگاه، شبکه و مسیر اصلی کاربر هم فرق می‌کنند. عدد باید با دلیل و محدودهٔ اندازه‌گیری ثبت شود. معرفی Performance Budget انواع معیار را توضیح می‌دهد. برای شاخص‌های فعلی تجربه، باید مرجع روز Core Web Vitals را جدا بخوانید و عددهای قدیمی یک راهنمای عمومی را بی‌تغییر منتقل نکنید.

حجم فایل و زمان تجربه دو شاهد متفاوت‌اند

دو صفحه با اندازهٔ برابر ممکن است زمان نمایش متفاوت داشته باشند. ترتیب دریافت، تأخیر سرور، اجرای JavaScript و زمان پیدا شدن منبع مهم اثر دارند. محدود کردن حجم، احتمال برخی هزینه‌ها را کم می‌کند، ولی زمان پاسخ را مستقیم تعیین نمی‌کند. در مقابل، زمان خوب در یک اجرای آزمایشگاهی هم نشان نمی‌دهد bundle بی‌دلیل بزرگ نشده است. ترکیب معیارهای ساخت و تجربه کمک می‌کند علت تغییر را بهتر پیدا کنیم. هر کدام باید محدودهٔ خودش را در گزارش حفظ کند.

مثلاً budget اندازهٔ فایل می‌تواند بگوید مجموع JavaScript اولیهٔ یک route از مقدار توافق‌شده بیشتر نشود. Budget تجربه می‌تواند برای همان route وضعیت LCP در شرایط مشخص را دنبال کند. اگر کل خروجی build را جمع می‌زنید، آن عدد با فایل‌هایی که کاربر در شروع route دریافت می‌کند یکی نیست. Chunkهای lazy ممکن است برای مسیر دیگری باشند. نام معیار را دقیق بنویسید تا گزارش مجموع مخزن با هزینهٔ ورود یک کاربر اشتباه نشود.

Core Web Vitals را با زمینه بخوانید

معیارهای فعلی Core Web Vitals شامل LCP برای تجربهٔ بارگذاری، INP برای پاسخ‌گویی تعامل و CLS برای پایداری بصری‌اند. در ارزیابی خوب، حدهای مرجع به ترتیب ۲٫۵ ثانیه، ۲۰۰ میلی‌ثانیه و ۰٫۱ هستند و دادهٔ صدک ۷۵ با تفکیک mobile و desktop اهمیت دارد. راهنمای فعلی Web Vitals این قرارداد را توضیح می‌دهد. این اعداد، اندازهٔ مجاز bundle نیستند و نتیجهٔ یک اجرای محلی را به دادهٔ واقعی کاربران تبدیل نمی‌کنند.

LCP زمان نمایش بزرگ‌ترین محتوای واجد شرایط را دنبال می‌کند؛ فقط مدت دانلود فایل JavaScript نیست. منبع دیر پیدا شده یا پاسخ کند سرور می‌تواند نتیجه را خراب کند، حتی اگر فایل کوچک باشد. مرجع LCP تعریف و نکته‌های اندازه‌گیری را بیان می‌کند. برای اصلاح، مسیر کامل رسیدن به محتوای مهم را بخوانید. حذف چند کیلوبایت از فایل نامرتبط ممکن است مسئلهٔ اصلی صفحه را تغییر ندهد.

واحد اندازه‌گیری را کنار عدد ثبت کنید

اندازهٔ raw، gzip و Brotli یکسان نیستند. KiB هم ۱۰۲۴ byte دارد و با kB ده‌دهی فرق می‌کند. اگر تیم سقف gzip دارد، سطح فشرده‌سازی و روش جمع کردن فایل‌ها باید مشخص باشد. اندازهٔ فشردهٔ محلی با byte واقعی دریافت‌شده در هر درخواست همیشه برابر نیست؛ header، encoding سرور و cache اثر دارند. برای بودجهٔ ساخت، اندازهٔ محلی شاهد قابل تکراری است، به شرط اینکه آن را مصرف شبکهٔ واقعی معرفی نکنیم.

تعداد route و الگوی دریافت هم مهم است. مجموع gzip مستقل هر فایل با فشرده کردن همهٔ فایل‌ها در یک archive متفاوت است. مرورگر فایل‌ها را مانند archive build دریافت نمی‌کند. در نمونهٔ زیر هر فایل جدا فشرده و اندازه‌ها جمع می‌شوند. این انتخاب برای گزارش خروجی ساده است و ادعای مدل کامل درخواست‌ها ندارد. فایل‌های map، فونت، تصویر و منابع ثالث در این نمونه شمرده نمی‌شوند؛ اگر به budget نیاز دارند، معیار جدا باید اضافه شود.

نمونهٔ کامل برای بررسی اندازهٔ خروجی

فایل زیر را با نام budget.mjs کنار پروژه ذخیره کنید. Node 24 لازم است و package خارجی ندارد. ابتدا با ابزار خود پروژه خروجی build واقعی بسازید؛ سپس node budget.mjs dist را اجرا کنید. اگر پوشهٔ خروجی نام دیگری دارد، همان مسیر را بدهید. نمونه فایل‌های js و css را در زیرپوشه‌ها پیدا می‌کند و مجموع اندازهٔ gzip هر گروه را با دو حد آموزشی مقایسه می‌کند. حدها تصمیم فرضی مثال‌اند، نه توصیهٔ قطعی برای پروژهٔ شما.

import { readdir, readFile } from 'node:fs/promises';
import { resolve, join, extname } from 'node:path';
import { gzipSync } from 'node:zlib';

const limits = { '.js': 200 * 1024, '.css': 50 * 1024 }; // Illustrative KiB limits, not universal standards.
async function collect(directory) {
  const files = [];
  for (const entry of await readdir(directory, { withFileTypes: true })) {
    const path = join(directory, entry.name);
    if (entry.isDirectory()) files.push(...await collect(path));
    else if (entry.isFile() && Object.hasOwn(limits, extname(entry.name))) files.push(path);
  }
  return files;
}
try {
  const root = resolve(process.argv[2] ?? 'dist');
  const files = await collect(root);
  if (!files.length) throw new Error('No .js or .css output files were found');
  const totals = { '.js': 0, '.css': 0 };
  for (const file of files) {
    const data = await readFile(file);
    const size = gzipSync(data, { level: 9 }).length;
    totals[extname(file)] += size;
    console.log(`${file}: raw=${data.length} bytes, gzip=${size} bytes`);
  }
  for (const extension of Object.keys(limits)) {
    console.log(`${extension}: ${totals[extension]} / ${limits[extension]} bytes`);
    if (totals[extension] > limits[extension]) process.exitCode = 1;
  }
} catch (error) {
  console.error(error.message);
  process.exitCode = 1;
}

اگر پوشه وجود نداشته باشد یا هیچ فایل مربوطی پیدا نشود، script شکست گزارش می‌کند. این رفتار مانع موفقیت بی‌معنای بررسی پوشهٔ اشتباه می‌شود. فایل symlink دنبال نمی‌شود و فقط فایل‌های معمولی و پوشه‌های معمولی در نظر گرفته می‌شوند. فایل با readFile وارد حافظه می‌شود، بنابراین این ابزار برای خروجی build متعارف طراحی شده، نه پردازش فایل‌های چندگیگابایتی. برای دادهٔ بزرگ، الگوی Node.js Streams نیاز متفاوتی را پاسخ می‌دهد.

کد خروج ناموفق هنگام عبور از حد به CI اجازه می‌دهد مرحله را شکست‌خورده بداند. Script چیزی را حذف یا منتشر نمی‌کند و فقط فایل‌های خروجی را می‌خواند. این مقاله روش اجرا را ارائه می‌دهد و گزارشی از اجرای اندازه‌گیری روی سایت یا build خاص ندارد. نام و واحد گزارش صریح‌اند: raw و gzip بر حسب byte. اگر روی پروژهٔ واقعی استفاده شد، مالک budget باید نام گروه و حد را متناسب با خروجی واقعی تغییر دهد.

Baseline را با شرایط قابل مقایسه نگه دارید

برای شروع، رفتار فعلی مسیرهای مهم را در شرایط مشخص ثبت کنید. اگر امروز یک ابزار با شبکهٔ شبیه‌سازی‌شده و فردا ابزار دیگری بدون محدودیت اجرا شود، اختلاف عدد به تنهایی اثر تغییر کد را نشان نمی‌دهد. نسخهٔ ابزار، مسیر، داده و شرایط باید قابل مقایسه باشند. در این مرحله می‌توان حد اولیه را نزدیک وضعیت قابل قبول تعیین کرد و برای اصلاح برنامه داشت. انتخاب حد غیرواقعی که همهٔ تغییرها را متوقف کند، معمولاً به نادیده گرفتن دائمی budget می‌انجامد.

Noise در زمان‌بندی آزمایشگاهی هم باید در سیاست شکست لحاظ شود. تکرار کنترل‌شده و مقایسهٔ روند، با افزایش بی‌هدف retry برای سبز کردن نتیجه فرق دارد. دادهٔ field شرایط کاربران واقعی را نشان می‌دهد ولی ممکن است برای تغییر تازه فوراً نمونهٔ کافی نداشته باشد. بهتر است گزارش بگوید چه شاهدی از build، چه شاهدی از lab و چه شاهدی از field داریم. یک امتیاز کلی را به جای همهٔ این اطلاعات قرار ندهید.

CI مرز را اجرا می‌کند، سیاست را تعیین نمی‌کند

بررسی اندازهٔ خروجی را می‌توان پس از build قرار داد. برای سنجش زمان و audit، Lighthouse CI تنظیم جدا می‌خواهد. مستندات Lighthouse CI قرارداد collect و assert را توضیح می‌دهد. در این مقاله آن ابزار نصب یا اجرا نشده است. نمونهٔ ما فقط budget فایل دارد؛ نتیجهٔ آن را تأیید LCP یا دسترس‌پذیری معرفی نمی‌کنیم. برای اتصال عمومی workflow، نوشتهٔ CI/CD فرانت‌اند مناسب است.

عبور از حد باید تصمیم قابل بررسی بسازد

وقتی حد شکسته شد، نخست علت را پیدا کنید. آیا dependency تازه واقعاً در chunk اولیه است؟ آیا import اشتباه کل package را وارد کرده؟ آیا دادهٔ بزرگی داخل کد تولید شده؟ ممکن است ویژگی جدید ارزش هزینه را داشته باشد. در آن صورت تغییر budget باید همراه دلیل، محدوده و مسئولیت ثبت شود، نه اینکه سقف بی‌صدا بالا برود. استثنای موقت هم تاریخ بازبینی و مالک می‌خواهد تا به قاعدهٔ همیشگی تبدیل نشود.

کار طراحی نیز روی هزینه اثر دارد: فونت‌های متعدد، جلوهٔ پیوسته و محتوای اولیهٔ بزرگ هم باید در گفتگو دیده شوند. Loading ظاهری پاسخ واقعی را سریع‌تر نمی‌کند؛ مقالهٔ وضعیت بارگذاری دربارهٔ تجربهٔ انتظار توضیح می‌دهد. حرکت رابط هم باید با هدف و هزینه انتخاب شود، همان‌طور که در Motion for React بررسی شده است. Budget مؤثر، یک قرارداد مشترک برای هزینهٔ تجربه است که با دادهٔ تازه بازبینی می‌شود.

واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!