التخزين المؤقت
إذًا نحن نستخدم webpack لتجميع تطبيقنا المعياري مما ينتج مجلد /dist جاهزًا للنشر. بمجرد نشر محتويات /dist على خادم، سيقوم العملاء (عادةً المتصفحات) بالاتصال بهذا الخادم للحصول على الموقع وملفاته. الخطوة الأخيرة قد تستغرق وقتًا، ولهذا تستخدم المتصفحات تقنية تُسمى التخزين المؤقت. هذا يسمح للمواقع بالتحميل بشكل أسرع مع تقليل حركة الشبكة غير الضرورية. ومع ذلك، قد يسبب ذلك بعض المشاكل عندما تحتاج إلى تحميل كود جديد.
يركز هذا الدليل على الإعدادات المطلوبة لضمان أن الملفات الناتجة من عملية تجميع webpack تبقى مخزنة مؤقتًا ما لم يتغير محتواها.
أسماء ملفات المخرجات
يمكننا استخدام إعداد output.filename الخاص بـ الاستبدالات لتحديد أسماء ملفات المخرجات. يوفر webpack طريقة لإنشاء قوالب لأسماء الملفات باستخدام سلاسل محاطة بأقواس تُسمى الاستبدالات. الاستبدال [contenthash] سيضيف قيمة hash فريدة بناءً على محتوى الملف. وعندما يتغير محتوى الملف، فإن [contenthash] سيتغير أيضًا.
لنقم بإعداد مشروعنا باستخدام المثال من البدء مع إضافة plugins من إدارة المخرجات، حتى لا نضطر إلى إدارة ملف index.html يدويًا:
project
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
├── /src
│ └── index.js
└── /node_moduleswebpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import HtmlWebpackPlugin from 'html-webpack-plugin';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin({
- title: 'Output Management',
+ title: 'Caching',
}),
],
output: {
- filename: 'bundle.js',
+ filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};تشغيل أمر البناء npm run build بهذا الإعداد يجب أن ينتج المخرجات التالية:
...
Asset Size Chunks Chunk Names
main.7e2c49a622975ebd9b7e.js 544 kB 0 [emitted] [big] main
index.html 197 bytes [emitted]
...كما ترى، اسم الحزمة الآن يعكس محتواها (عبر قيمة الـ hash). إذا قمنا بتشغيل البناء مرة أخرى بدون أي تغييرات، فمن المفترض أن يبقى اسم الملف كما هو. ولكن إذا قمنا بالبناء مرة أخرى، فقد نجد أن هذا ليس صحيحًا:
...
Asset Size Chunks Chunk Names
main.205199ab45963f6a62ec.js 544 kB 0 [emitted] [big] main
index.html 197 bytes [emitted]
...هذا لأن webpack يضمّن بعض الشيفرات الأساسية، وبالتحديد الـ runtime والـ manifest، داخل حزمة الإدخال.
استخراج الشيفرات الأساسية
كما تعلمنا في تقسيم الكود، يمكن استخدام SplitChunksPlugin لفصل الوحدات إلى حزم منفصلة. يوفر webpack ميزة تحسين لتقسيم كود الـ runtime إلى حزمة منفصلة باستخدام الخيار optimization.runtimeChunk. قم بضبطه إلى single لإنشاء حزمة runtime واحدة لكل الحزم:
webpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import HtmlWebpackPlugin from 'html-webpack-plugin';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin({
title: 'Caching',
}),
],
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
+ optimization: {
+ runtimeChunk: 'single',
+ },
};لنقم بتشغيل البناء مرة أخرى لرؤية حزمة runtime المستخرجة:
Hash: 82c9c385607b2150fab2
Version: webpack 4.12.0
Time: 3027ms
Asset Size Chunks Chunk Names
runtime.cc17ae2a94ec771e9221.js 1.42 KiB 0 [emitted] runtime
main.e81de2cf758ada72f306.js 69.5 KiB 1 [emitted] main
index.html 275 bytes [emitted]
[1] (webpack)/buildin/module.js 497 bytes {1} [built]
[2] (webpack)/buildin/global.js 489 bytes {1} [built]
[3] ./src/index.js 309 bytes {1} [built]
+ 1 hidden moduleمن الممارسات الجيدة أيضًا فصل المكتبات الخارجية مثل lodash أو react إلى حزمة vendor منفصلة لأنها أقل عرضة للتغيير مقارنة بالكود المحلي الخاص بنا. هذه الخطوة ستسمح للعملاء بطلب بيانات أقل من الخادم للبقاء محدثين.
يمكن تنفيذ ذلك باستخدام خيار cacheGroups الخاص بـ SplitChunksPlugin كما هو موضح في المثال 2 من SplitChunksPlugin. لنقم بإضافة optimization.splitChunks مع cacheGroups بالمعاملات التالية ثم نقوم بالبناء:
webpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import HtmlWebpackPlugin from 'html-webpack-plugin';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin({
title: 'Caching',
}),
],
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
optimization: {
runtimeChunk: 'single',
+ splitChunks: {
+ cacheGroups: {
+ vendor: {
+ test: /[\\/]node_modules[\\/]/,
+ name: 'vendors',
+ chunks: 'all',
+ },
+ },
+ },
},
};لنقم بالبناء مرة أخرى لرؤية حزمة vendor الجديدة:
...
Asset Size Chunks Chunk Names
runtime.cc17ae2a94ec771e9221.js 1.42 KiB 0 [emitted] runtime
vendors.a42c3ca0d742766d7a28.js 69.4 KiB 1 [emitted] vendors
main.abf44fedb7d11d4312d7.js 240 bytes 2 [emitted] main
index.html 353 bytes [emitted]
...يمكننا الآن ملاحظة أن حزمة main لم تعد تحتوي على كود vendor من مجلد node_modules وأصبح حجمها 240 bytes فقط!
معرفات الوحدات
لنقم بإضافة وحدة جديدة print.js إلى مشروعنا:
project
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
├── /src
│ ├── index.js
+│ └── print.js
└── /node_modulesprint.js
+ export default function print(text) {
+ console.log(text);
+ };src/index.js
import _ from 'lodash';
+ import Print from './print';
function component() {
const element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ element.onclick = Print.bind(null, 'Hello webpack!');
return element;
}
document.body.appendChild(component());عند تشغيل البناء مرة أخرى، نتوقع أن يتغير hash الخاص بحزمة main فقط، ولكن...
...
Asset Size Chunks Chunk Names
runtime.1400d5af64fc1b7b3a45.js 5.85 kB 0 [emitted] runtime
vendor.a7561fb0e9a071baadb9.js 541 kB 1 [emitted] [big] vendor
main.b746e3eb72875af2caa9.js 1.22 kB 2 [emitted] main
index.html 352 bytes [emitted]
...... يمكننا رؤية أن الثلاثة جميعًا قد تغيروا. السبب هو أن كل module.id يتم زيادته افتراضيًا بناءً على ترتيب التحليل. وهذا يعني أنه عندما يتغير ترتيب التحليل، ستتغير المعرفات أيضًا. للتلخيص:
- حزمة
mainتغيرت بسبب محتواها الجديد. - حزمة
vendorتغيرت لأنmodule.idالخاص بها تغير. - وحزمة
runtimeتغيرت لأنها تحتوي الآن على مرجع إلى وحدة جديدة.
الأول والأخير متوقعان، لكننا نريد إصلاح hash الخاص بـ vendor. لنستخدم optimization.moduleIds مع الخيار 'deterministic':
webpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import HtmlWebpackPlugin from 'html-webpack-plugin';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
plugins: [
new HtmlWebpackPlugin({
title: 'Caching',
}),
],
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
optimization: {
+ moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
};الآن، مهما أضفنا من تبعيات محلية جديدة، يجب أن يبقى hash الخاص بـ vendor ثابتًا بين عمليات البناء:
...
Asset Size Chunks Chunk Names
main.216e852f60c8829c2289.js 340 bytes 0 [emitted] main
vendors.55e79e5927a639d21a1b.js 69.5 KiB 1 [emitted] vendors
runtime.725a1a51ede5ae0cfde0.js 1.42 KiB 2 [emitted] runtime
index.html 353 bytes [emitted]
Entrypoint main = runtime.725a1a51ede5ae0cfde0.js vendors.55e79e5927a639d21a1b.js main.216e852f60c8829c2289.js
...والآن لنقم بتعديل src/index.js لإزالة تلك التبعية الإضافية مؤقتًا:
src/index.js
import _ from 'lodash';
- import Print from './print';
+ // import Print from './print';
function component() {
const element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
- element.onclick = Print.bind(null, 'Hello webpack!');
+ // element.onclick = Print.bind(null, 'Hello webpack!');
return element;
}
document.body.appendChild(component());وأخيرًا قم بتشغيل البناء مرة أخرى:
...
Asset Size Chunks Chunk Names
main.ad717f2466ce655fff5c.js 274 bytes 0 [emitted] main
vendors.55e79e5927a639d21a1b.js 69.5 KiB 1 [emitted] vendors
runtime.725a1a51ede5ae0cfde0.js 1.42 KiB 2 [emitted] runtime
index.html 353 bytes [emitted]
Entrypoint main = runtime.725a1a51ede5ae0cfde0.js vendors.55e79e5927a639d21a1b.js main.ad717f2466ce655fff5c.js
...يمكننا ملاحظة أن كلا عمليتي البناء استخدمتا القيمة 55e79e5927a639d21a1b في اسم ملف حزمة vendor.
الخلاصة
قد يكون التخزين المؤقت معقدًا، لكن الفائدة التي يقدمها لمستخدمي التطبيق أو الموقع تجعل الجهد مستحقًا. راجع قسم Further Reading أدناه لمعرفة المزيد.



