مشکل Memory Leak هنگام استفاده از hash در Webpack

در این مقاله به بررسی یک مشکل رایج در محیط توسعه با


در این مقاله به بررسی یک مشکل رایج در محیط توسعه با Webpack می‌پردازیم که می‌تواند منجر به نشت حافظه (Memory Leak) شود، آن هم زمانی که از ویژگی [hash] برای نام‌گذاری فایل‌ها استفاده می‌کنیم.


🎯 مقدمه: چرا از Hash استفاده می‌کنیم؟

مرورگرها فایل‌های استاتیک مثل JavaScript، CSS و تصاویر را کش (cache) می‌کنند تا در دفعات بعدی، نیازی به دانلود مجدد نباشد. اما اگر تغییری در کد انجام دهیم و نام فایل ثابت بماند، مرورگر نسخه کش‌شده را نمایش می‌دهد و تغییرات جدید دیده نمی‌شوند.

برای حل این مشکل، Webpack قابلیت اضافه کردن hash به نام فایل‌ها را ارائه می‌دهد:

  • [hash]
  • [chunkhash]
  • [contenthash]

با استفاده از این ویژگی‌ها، نام فایل با هر تغییر در محتوا عوض می‌شود و مرورگر فایل جدید را دانلود می‌کند.


🧠 مشکل: Memory Leak در محیط توسعه (Development)

در ابتدا Webpack از [hash] استفاده می‌کرد. اما در محیط توسعه باعث ایجاد مشکل شد:

❗ مشکل دقیقاً چیست؟

با هر بار تغییر در فایل‌ها:

  • Webpack یک بیلد جدید تولید می‌کند.
  • نام فایل‌ها به‌دلیل تغییر hash عوض می‌شود.
  • فایل‌های جدید در حافظه (Memory) ذخیره می‌شوند.
  • فایل‌های قدیمی آزاد نمی‌شوند.

مثال:

Build 1 → main.a1b2c3.js
Build 2 → main.d4e5f6.js

main.a1b2c3.js هنوز در حافظه باقی مانده و main.d4e5f6.js هم به آن اضافه شده است.

🔁 چرا حافظه آزاد نمی‌شود؟

Webpack Dev Server فایل‌ها را در حافظه نگه می‌دارد تا سریع‌تر سرو شود. پلاگین‌هایی مانند:

  • HtmlWebpackPlugin
  • MiniCssExtractPlugin

خروجی را در حافظه نگه می‌دارند تا آن‌ها را پردازش کنند. این پلاگین‌ها معمولاً Referenceهایی به فایل‌ها نگه می‌دارند تا بتوانند آن‌ها را مقایسه یا تحلیل کنند.

تا وقتی که این Referenceها وجود دارند، Garbage Collector فایل‌ها را آزاد نمی‌کند. چون از دید JavaScript، هنوز به آن‌ها نیاز هست.

نتیجه؟ حافظه‌ی Heap به‌مرور پر از اشیای:

  • File
  • Blob
  • Referenceهای زنجیره‌ای

می‌شود و در نهایت به نشت حافظه منجر می‌گردد.


✅ آیا در Production هم این مشکل وجود دارد؟

خیر. در حالت Production:

  • Webpack فقط یک بار اجرا می‌شود.
  • هیچ Watch یا DevServer فعالی وجود ندارد.
  • فایل‌ها روی دیسک ذخیره می‌شوند.
  • بعد از پایان build، process خاتمه می‌یابد و حافظه آزاد می‌شود.

پس در حالت عادی، [hash] در production مشکلی ایجاد نمی‌کند.


🛠️ نکاتی برای توسعه‌دهندگان

  • اگر در محیط توسعه هستید، از [hash] استفاده نکنید.
  • در عوض، از [contenthash] یا [chunkhash] فقط در production استفاده کنید.
  • می‌توانید devtool را روی eval یا cheap-module-source-map تنظیم کنید تا performance بهتری داشته باشید.
  • از قابلیت persistent caching در Webpack 5 فقط با مدیریت خوب فایل‌های کش استفاده کنید. در غیر این صورت، باعث پر شدن دیسک می‌شود.

🔚 نتیجه‌گیری

استفاده از [hash] در Webpack مزایای مهمی برای کش دارد، اما در محیط توسعه ممکن است باعث نشت حافظه شود، چون فایل‌های بی‌استفاده در حافظه باقی می‌مانند و آزاد نمی‌شوند. راه‌حل ساده است: در dev از hash استفاده نکنید و فقط در production آن را فعال کنید.


آیا تجربه‌ای با Memory Leak در Webpack داشتید؟ توی کامنت‌ها یا شبکه‌های اجتماعی بنویسید 🌐

مرورگر فایل‌های استاتیک (JS, CSS, Images, ...) رو از سرور دریافت می‌کنه و اون‌ها رو cache می‌کنه تا در درخواست‌های بعدی نیازی به دانلود مجدد نباشه.

در Webpack 5 این قابلیت اضافه شد که cache رو روی دیسک ذخیره کنه، و بین buildهای مختلف حفظ کنه. استفاده از این قابلیت در صورت پاک نکردن فایل‌های قبلی از روی دیسک بعد از هر بیلد باعث می‌شه فایل‌های روی دیسک زیاد بشن → فضای دیسک پر می‌شه!

با تغییر هر فایل تمامی فایل‌ها دوباره ایجاد می‌شن با hash جدید و فایل‌های قبلی در حافظه باقی می‌مونن.

فرض کن دو build پیاپی انجام می‌دی:

First build → main.a1b2c3.js
Second build → main.d4e5f6.js

و فایل main.a1b2c3.js هنوز در حافظه یا سیستم باقی مونده.

در هنگام rebuild، Webpack فایل‌ها رو به‌جای ذخیره روی دیسک، در حافظه نگه می‌داره. با هر بار build، حافظه‌ی مصرفی مدام بیشتر و بیشتر می‌شه چون فایل‌های قدیمی پاک نمی‌شن.

به زبان ساده: Webpack یک فایل جدید در memory می‌سازه ولی فایل قدیمی رو از memory آزاد نمی‌کنه → Memory Leak.

تا زمانی که process (یعنی webpack-dev-server) restart نشه، هیچ‌کدوم از فایل‌های قبلی آزاد نمی‌شن.

پلاگین‌هایی مثل HtmlWebpackPlugin، MiniCssExtractPlugin:

این پلاگین‌ها هنگام build، خروجی‌ها رو در حافظه نگه‌می‌دارن برای پردازش HTML، CSS و... اگر در هر build خروجی جدید با hash متفاوت بیاد، اون پلاگین‌ها فایل جدید رو اضافه می‌کنن ولی قبلی رو آزاد نمی‌کنن.

حافظه Heap پر از اشیای File و Blob و Referenceهای زنجیره‌ای می‌شه که باعث می‌شن GC (garbage collector) اون‌ها رو جمع نکنه.

🔍 نگاه عمیق‌تر: چرا GC نمی‌تونه فایل‌های قدیمی رو پاک کنه؟

هر object جاوااسکریپت (مثل buffer فایل) تا زمانی که Reference بهش وجود داره، توسط Garbage Collector پاک نمی‌شه. پلاگین‌ها و cache loaderها درون خودشون یک reference به فایل قبلی نگه می‌دارن (برای diff یا comparison یا track changes). چون [hash] باعث می‌شه فایل‌ها هر بار جدید باشن، chain این referenceها بزرگ و بزرگ‌تر می‌شه.

این پلاگین صرفاً وظیفه دارد فایل را کپی کند و با هر بار بیلد فایل را تغییر نمی‌دهد. این کار باعث نمی‌شود نشت حافظه اتفاق بیفتد.

❓ آیا استفاده از [hash] در محیط Production باعث Memory Leak می‌شود؟

✅ پاسخ کوتاه:

خیر، در حالت عادی در محیط Production استفاده از [hash] باعث memory leak نمی‌شود، چون:

  • در production، Webpack فقط یک بار اجرا می‌شود (نه به‌صورت دائمی مثل dev-server).
  • هیچ Watch یا حافظه‌ای برای نگه‌داشتن فایل‌ها وجود ندارد.
  • فایل‌ها به‌صورت فیزیکی روی دیسک نوشته می‌شوند.
  • بعد از پایان build، process از بین می‌رود و حافظه پاک می‌شود.

در حالت dev-server:

وقتی پروژه بالا میاد با تغییر در حتی یک فایل، پس از ری‌بیلد شدن فقط همان فایل بررسی و بیلد می‌شود و دوباره ایجاد می‌شود نه همه فایل‌ها. ولی چون اسم فایل hash داره و تغییر کرده، فایل قبلی توی حافظه باقی می‌مونه.


📦 حالا کی باید از [hash] استفاده کنیم؟

معمولاً موقعی که داری فایل‌هایی مثل JavaScript و CSS رو برای production build می‌کنی، از [hash] یا [contenthash] استفاده کن.

output: {
  filename: '[name].[contenthash].js',
  clean: true
}

اینطوری:

  • اگه فایل تغییر کنه → hash تغییر می‌کنه → اسم فایل تغییر می‌کنه → مرورگر می‌فهمه نسخه‌ی جدید اومده
  • اگه فایل تغییر نکنه → hash ثابت می‌مونه → فایل از cache استفاده می‌شه

🔁 مرورگر چطوری cache می‌کنه؟

مرورگر از HTTP Headers استفاده می‌کنه که از طرف سرور فرستاده می‌شن:

🕐 Cache-Control: max-age

Cache-Control: public, max-age=31536000

یعنی این فایل تا یک سال cache بشه. اگه فایل hash داشته باشه، مشکلی پیش نمیاد چون فایل جدید hash جدید داره و مرورگر نسخه قبلی رو پاک می‌کنه.

🏷️ ETag و Last-Modified

  • ETag: یه hash از محتوا رو برمی‌گردونه. مرورگر دفعه بعد می‌پرسه "آیا هنوز همون فایله؟" اگه بله → سرور 304 می‌ده.
  • Last-Modified: تاریخ آخرین تغییر رو چک می‌کنه.

ولی وقتی hash تو اسم فایل هست، دیگه نیازی به اینا نیست چون خود اسم فایل داره به مرورگر می‌گه که این یه نسخه‌ی جدیده.


🧠 مثال کامل Webpack و Express برای کش درست:

// webpack.prod.js
module.exports = {
  mode: 'production',
  output: {
    filename: '[name].[contenthash].js',
    clean: true,
  },
};
// Express
app.use(express.static('dist', {
  maxAge: '1y',
  etag: false
}));

✅ جمع‌بندی نهایی

| ویژگی | Production | Dev Server | | ---------------------------- | ------------------- | ---------------------------------------------- | | [hash] در filename | ✔ عالی برای cache | ❌ باعث memory leak | | Cache مرورگر | ✔ دقیق و قابل کنترل | ❌ فقط برای production مهمه | | پاک‌سازی حافظه | ❌ نیاز نداره | ✔ باید manual انجام بشه یا از hash استفاده نشه | | پلاگین‌ها memory نگه می‌دارن | ❌ | ✔ |


وقتی داری توسعه می‌دی، از hash استفاده نکن. فقط یه اسم ساده بذار مثل [name].js. ولی توی production، hash خیلی مفیده چون دقیقاً به مرورگر می‌گه که آیا این فایل تغییر کرده یا نه. اگرم dev-server داری و دیدی حافظه کم میاد یا process سنگین شده، بدون ممکنه memory leak از همین hash باشه.