۵ دقیقه مطالعه
مشکل Memory Leak هنگام استفاده از hash در Webpack
در این مقاله یک مشکل رایج Memory Leak در Webpack هنگام استفاده از contenthash در محیط توسعه بررسی میشود و راهحلی عملی ارائه میشود.
در این مقاله یک مشکل رایج در محیط توسعه Webpack را بررسی میکنیم؛ مشکلی که ممکن است هنگام استفاده از قابلیت Cache Busting و قراردادن [hash] در نام فایلها به نشت حافظه (Memory Leak) منجر شود.
کشکردن فایلها چه مشکلی را حل میکند؟
مرورگرها فایلهای استاتیک مانند JavaScript، CSS، Font و تصویر را Cache میکنند تا در بازدیدهای بعدی نیازی به دانلود دوباره آنها نباشد. این کار سرعت بارگذاری را افزایش میدهد.
اما اگر محتوای یک فایل تغییر کند و نام آن ثابت بماند، مرورگر ممکن است نسخه Cacheشده را نمایش دهد و کاربر تغییرات جدید را نبیند. برای حل این مشکل، Webpack میتواند یک hash را به نام فایل اضافه کند:
[hash][chunkhash][contenthash]
با این روش، پس از تغییر محتوا نام فایل نیز تغییر میکند و مرورگر فایل جدید را دانلود میکند.
مشکل Memory Leak در محیط توسعه
Webpack در ابتدا بیشتر از [hash] استفاده میکرد. در محیط توسعه، این روش میتواند مشکلساز شود؛ زیرا برای هر Build یک Hash جدید ساخته میشود و به نام فایلها اضافه میگردد.
در نتیجه، فایلهای جدید در حافظه تولید میشوند، اما فایلهای قبلی ممکن است تا زمانی که Reference آنها آزاد نشده است، در حافظه باقی بمانند:
- Webpack یک Build جدید تولید میکند.
- نام فایلها بهدلیل تغییر
hashتغییر میکند. - فایلهای جدید در حافظه ذخیره میشوند.
- فایلهای قبلی هنوز توسط DevServer یا Loaderها نگهداری میشوند.
برای مثال:
Build 1 → main.a1b2c3.js
Build 2 → main.d4e5f6.js
در این حالت، main.a1b2c3.js ممکن است همچنان در حافظه باقی بماند و فایل جدید نیز به آن اضافه شود.
آیا این مشکل در Production هم وجود دارد؟
در حالت معمول، خیر. در محیط Production:
- Webpack معمولاً فقط یکبار اجرا میشود.
- Watch یا DevServer دائمی فعال نیست.
- فایلها روی دیسک ذخیره میشوند.
- پس از پایان Build، Process خاتمه پیدا میکند و حافظه آزاد میشود.
بنابراین استفاده از [hash] در Production بهخودیخود باعث Memory Leak نمیشود.
پیشنهادهایی برای توسعهدهندگان
- در محیط توسعه از
[hash]استفاده نکنید. [contenthash]یا[chunkhash]را به Production محدود کنید.- برای بهبود Performance، مقدار
devtoolرا متناسب با نیاز پروژه تنظیم کنید؛ برای مثالevalیاcheap-module-source-map. - از Persistent Caching در Webpack 5 با مدیریت درست فایلهای Cache استفاده کنید؛ در غیر این صورت ممکن است فضای دیسک بیدلیل مصرف شود.
جمعبندی
استفاده از [hash] در Webpack برای Cache Busting مزایای مهمی دارد، اما در محیط توسعه و هنگام نگهداری خروجیهای متعدد در حافظه میتواند باعث افزایش مصرف حافظه شود. راهکار ساده این است که در Development از نام فایل ساده استفاده کنیم و Hash را برای Production فعال نگه داریم.
اگر با مفاهیم نسخهبندی معنایی (SemVer) در npm آشنا نیستید، مقاله SemVer در npm به زبان ساده را بخوانید.
نگاهی عمیقتر: چرا Garbage Collector فایلهای قدیمی را پاک نمیکند؟
مرورگر فایلهای استاتیک مانند JS، CSS و Image را از سرور دریافت و Cache میکند. در سمت Webpack نیز پلاگینهایی مانند HtmlWebpackPlugin و MiniCssExtractPlugin خروجیها را هنگام Build برای پردازش HTML و CSS در حافظه نگه میدارند.
اگر در هر Build خروجی جدیدی با Hash متفاوت تولید شود، پلاگینها ممکن است خروجی جدید را اضافه کنند، در حالی که خروجی قبلی هنوز Reference داشته باشد. هر Object جاوااسکریپت تا زمانی که Reference فعالی به آن وجود داشته باشد، توسط Garbage Collector پاک نمیشود.
برخی Pluginها و Cache Loaderها برای Diff، مقایسه یا Track کردن تغییرات، Reference خروجیهای قبلی را نگه میدارند. در چنین شرایطی زنجیره Referenceها میتواند بهمرور بزرگتر شود.
آیا استفاده از [hash] در Production باعث Memory Leak میشود؟
در حالت عادی، خیر. در Production:
- Webpack فقط یکبار اجرا میشود، نه بهصورت دائمی مانند DevServer.
- فایلها بهصورت فیزیکی روی دیسک نوشته میشوند.
- پس از پایان Build، Process از بین میرود و حافظه آزاد میشود.
وضعیت در DevServer
در DevServer، با تغییر فایل ممکن است یک Build جدید انجام شود. اگر نام فایل به Hash وابسته باشد، خروجی جدید نام متفاوتی خواهد داشت و خروجی قبلی ممکن است تا زمان آزادشدن Reference در حافظه باقی بماند.
چه زمانی از [hash] استفاده کنیم؟
معمولاً برای Build فایلهای JavaScript و CSS در Production از [hash] یا [contenthash] استفاده میشود:
output: {
filename: '[name].[contenthash].js',
clean: true
}
در این حالت:
- اگر فایل تغییر کند، Hash و نام فایل تغییر میکند و مرورگر نسخه جدید را دریافت میکند.
- اگر فایل تغییر نکند، Hash ثابت میماند و مرورگر میتواند از نسخه Cacheشده استفاده کند.
مرورگر چگونه Cache میکند؟
مرورگر از HTTP Headerهایی استفاده میکند که سرور ارسال میکند.
Cache-Control: max-age
Cache-Control: public, max-age=31536000
این Header میگوید فایل میتواند تا یک سال Cache شود. اگر نام فایل شامل Hash باشد، نسخه جدید نام متفاوتی دارد و با نسخه قبلی اشتباه نمیشود.
ETag و Last-Modified
- ETag: یک شناسه مبتنی بر محتوای فایل برمیگرداند. مرورگر در درخواست بعدی میپرسد آیا فایل تغییر کرده است یا نه؛ اگر تغییر نکرده باشد، سرور پاسخ
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,
})
);
در زمان توسعه، از نام سادهای مانند [name].js استفاده کنید. در Production، contenthash کمک میکند مرورگر بهدرستی تشخیص دهد کدام فایل تغییر کرده و کدام نسخه را میتوان از Cache خواند.
نوشتههای مرتبط
شاید این نوشتهها هم برای شما مفید باشند.
نسخهبندی معنایی (Semantic Versioning)
مروری سریع بر نسخهبندی معنایی و دلیل استفاده از آن در پروژههای نرمافزاری.
خواندن نوشتهSemVer در npm به زبان ساده
در این مقاله میبینیم علامتهای ^ و ~ در نسخهگذاری npm چه معنایی دارند و npm install چگونه با آنها رفتار میکند.
خواندن نوشته