SplitChunksPlugin
في البداية، كانت chunks، والـ modules المستوردة داخلها، مرتبطة بعلاقة parent-child داخل graph الداخلي في webpack. كان CommonsChunkPlugin يُستخدم لتجنب تكرار dependencies بينها، لكن لم تكن هناك مساحة كبيرة لتحسينات إضافية.
منذ webpack v4، تمت إزالة CommonsChunkPlugin واستُبدل بخيار optimization.splitChunks.
القيم الافتراضية
يعمل SplitChunksPlugin بشكل جيد لمعظم المستخدمين بدون إعدادات إضافية.
افتراضيًا، يؤثر فقط في chunks التي تُحمّل عند الطلب، لأن تغيير initial chunks قد يؤثر في وسوم script التي يجب أن يتضمنها ملف HTML لتشغيل المشروع.
يقسم webpack chunks تلقائيًا بناءً على هذه الشروط:
- يمكن مشاركة chunk الجديد، أو أن modules قادمة من مجلد
node_modules. - سيكون chunk الجديد أكبر من 20kb قبل min+gz.
- أقصى عدد للطلبات المتوازية عند تحميل chunks عند الطلب سيكون أقل من أو يساوي 30.
- أقصى عدد للطلبات المتوازية عند التحميل الأولي للصفحة سيكون أقل من أو يساوي 30.
عند محاولة تحقيق الشرطين الأخيرين، تُفضّل chunks الأكبر.
الإعداد
يوفر webpack مجموعة خيارات للمطورين الذين يريدون تحكمًا أكبر بهذه الوظيفة.
optimization.splitChunks
يمثل كائن الإعداد هذا السلوك الافتراضي لـ SplitChunksPlugin.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
chunks: "async",
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};splitChunks.automaticNameDelimiter
string = '~'
افتراضيًا، يولد webpack الأسماء باستخدام origin واسم chunk، مثل vendors~main.js. يتيح لك هذا الخيار تحديد الفاصل المستخدم في الأسماء المولدة.
splitChunks.chunks
string = 'async' function (chunk) RegExp
يحدد هذا الخيار chunks التي سيتم اختيارها للتحسين. عندما تمرر string، تكون القيم الصالحة هي all وasync وinitial. تمرير all قد يكون قويًا جدًا، لأنه يعني إمكانية مشاركة chunks حتى بين chunks غير المتزامنة والمتزامنة.
لاحظ أن هذا الخيار يُطبّق أيضًا على fallback cache group، أي splitChunks.fallbackCacheGroup.chunks.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
// تضمين كل أنواع chunks
chunks: "all",
},
},
};بدلًا من ذلك، يمكنك تمرير دالة لمزيد من التحكم. قيمة الإرجاع ستحدد هل يتم تضمين كل chunk أم لا.
export default {
// ...
optimization: {
splitChunks: {
chunks(chunk) {
// استبعاد `my-excluded-chunk`
return chunk.name !== "my-excluded-chunk";
},
},
},
};إذا كنت تستخدم webpack 5.86.0 أو أحدث، يمكنك أيضًا تمرير regular expression:
export default {
// ...
optimization: {
splitChunks: {
chunks: /foo/,
},
},
};splitChunks.maxAsyncRequests
number = 30
أقصى عدد للطلبات المتوازية عند التحميل عند الطلب.
splitChunks.maxInitialRequests
number = 30
أقصى عدد للطلبات المتوازية عند entry point.
splitChunks.defaultSizeTypes
[string] = ['javascript', 'unknown']
يضبط أنواع الحجم التي تُستخدم عندما تكون الأحجام أرقامًا.
splitChunks.minChunks
number = 1
الحد الأدنى لعدد المرات التي يجب أن يكون module مشتركًا فيها بين chunks قبل تقسيمه.
splitChunks.hidePathInfo
boolean
يمنع كشف معلومات المسار عند إنشاء أسماء للأجزاء المقسمة بواسطة maxSize.
splitChunks.minSize
number = 20000 { [index: string]: number }
الحد الأدنى للحجم، بالبايت، المطلوب لإنشاء chunk.
splitChunks.minSizeReduction
number { [index: string]: number }
الحد الأدنى المطلوب لتقليل حجم chunk الرئيسي، أو bundle، بالبايت حتى يتم إنشاء chunk جديد. أي إذا لم يقلل التقسيم حجم bundle الرئيسي بالمقدار المحدد من البايت، فلن يتم التقسيم حتى لو تحققت قيمة splitChunks.minSize.
splitChunks.enforceSizeThreshold
splitChunks.cacheGroups.{cacheGroup}.enforceSizeThreshold
number = 50000
عتبة الحجم التي عندها يُفرض التقسيم ويتم تجاهل القيود الأخرى، مثل minRemainingSize وmaxAsyncRequests وmaxInitialRequests.
splitChunks.minRemainingSize
splitChunks.cacheGroups.{cacheGroup}.minRemainingSize
number = 0
أُضيف خيار splitChunks.minRemainingSize في webpack 5 لتجنب modules بحجم صفري، عبر التأكد من أن الحد الأدنى لحجم chunk المتبقي بعد التقسيم أعلى من حد معين.
تعتمد القيمة الافتراضية لـ splitChunks.minRemainingSize على mode:
| Mode | الافتراضي |
|---|---|
"production" | قيمة splitChunks.minSize |
"development" | 0 |
"none" | قيمة splitChunks.minSize |
لا تحتاج غالبًا إلى تحديده يدويًا إلا في حالات نادرة تتطلب تحكمًا عميقًا.
splitChunks.layer
splitChunks.cacheGroups.{cacheGroup}.layer
RegExp string function
يسند modules إلى cache group حسب module layer.
splitChunks.maxSize
number = 0
استخدام maxSize، سواء بشكل عام عبر optimization.splitChunks.maxSize أو لكل cache group عبر optimization.splitChunks.cacheGroups[x].maxSize أو للـ fallback cache group عبر optimization.splitChunks.fallbackCacheGroup.maxSize، يخبر webpack أن يحاول تقسيم chunks الأكبر من maxSize بايت إلى أجزاء أصغر. ستكون الأجزاء على الأقل بحجم minSize بجانب maxSize.
الخوارزمية deterministic، وتغييرات modules سيكون لها تأثير محلي فقط. لذلك تكون مناسبة عند استخدام long term caching ولا تحتاج إلى records. maxSize مجرد تلميح، وقد لا يلتزم به webpack إذا كانت modules أكبر من maxSize أو إذا كان التقسيم سيخالف minSize.
إذا كان chunk يملك اسمًا مسبقًا، فسيحصل كل جزء على اسم جديد مشتق من ذلك الاسم. وبحسب قيمة optimization.splitChunks.hidePathInfo، سيضيف webpack مفتاحًا مشتقًا من اسم أول module أو hash منه.
خيار maxSize مخصص للاستخدام مع HTTP/2 وlong term caching. يزيد عدد الطلبات لتحسين caching. ويمكن استخدامه أيضًا لتقليل حجم الملف وتسريع إعادة build.
splitChunks.maxAsyncSize
number
مثل maxSize، يمكن تطبيق maxAsyncSize بشكل عام عبر splitChunks.maxAsyncSize، أو على cacheGroups عبر splitChunks.cacheGroups.{cacheGroup}.maxAsyncSize، أو على fallback cache group عبر splitChunks.fallbackCacheGroup.maxAsyncSize.
الفرق بين maxAsyncSize وmaxSize هو أن maxAsyncSize يؤثر فقط في chunks التي تُحمّل عند الطلب.
splitChunks.maxInitialSize
number
مثل maxSize، يمكن تطبيق maxInitialSize بشكل عام عبر splitChunks.maxInitialSize، أو على cacheGroups عبر splitChunks.cacheGroups.{cacheGroup}.maxInitialSize، أو على fallback cache group عبر splitChunks.fallbackCacheGroup.maxInitialSize.
الفرق بين maxInitialSize وmaxSize هو أن maxInitialSize يؤثر فقط في initial load chunks.
splitChunks.name
boolean = false function (module, chunks, cacheGroupKey) => string string
متاح أيضًا لكل cacheGroup: splitChunks.cacheGroups.{cacheGroup}.name.
اسم split chunk. تمرير false سيبقي الاسم نفسه للـ chunks حتى لا تتغير الأسماء بلا حاجة. هذه هي القيمة الموصى بها في production builds.
تمرير string أو دالة يسمح باستخدام اسم مخصص. تحديد string، أو دالة ترجع دائمًا string نفسه، سيدمج كل common modules وvendors داخل chunk واحد. قد يؤدي هذا إلى تنزيل أولي أكبر وإبطاء تحميل الصفحات.
إذا اخترت تحديد دالة، فقد تكون الخاصية chunk.name مفيدة جدًا في اختيار اسم chunk، حيث يكون chunk عنصرًا من array المسماة chunks.
إذا طابق splitChunks.name اسم entry point، فسيتم دمج entry-point chunk وcache group داخل chunk واحد.
main.js
import _ from "lodash";
console.log(_.join(["Hello", "webpack"], " "));webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
// cacheGroupKey هنا هو `commons` لأنه مفتاح cacheGroup
name(module, chunks, cacheGroupKey) {
const moduleFileName = module
.identifier()
.split("/")
.reduceRight((item) => item);
const allChunksNames = chunks.map((item) => item.name).join("~");
return `${cacheGroupKey}-${allChunksNames}-${moduleFileName}`;
},
chunks: "all",
},
},
},
},
};تشغيل webpack بهذا الإعداد لـ splitChunks سيخرج أيضًا chunk من المجموعة common بالاسم التالي: commons-main-lodash.js.e7519d2bb8777058fa27.js، والـ hash هنا مثال من output حقيقي.
splitChunks.usedExports
splitChunks.cacheGroups{cacheGroup}.usedExports
boolean = true
يحدد exports المستخدمة بواسطة modules لتغيير أسماء exports، وحذف exports غير المستخدمة، وتوليد كود أكثر كفاءة.
عندما تكون القيمة true: يتم تحليل used exports لكل runtime. وعندما تكون "global": يتم تحليل exports بشكل عام لكل runtimes معًا.
splitChunks.cacheGroups
يمكن لـ cache groups أن ترث أو تتجاوز أي خيار من splitChunks.*، لكن test وpriority وreuseExistingChunk لا يمكن إعدادها إلا على مستوى cache group. لتعطيل أي cache groups افتراضية، اضبطها على false.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
default: false,
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.priority
number = -20
يمكن أن ينتمي module إلى عدة cache groups. ستفضّل عملية optimization الـ cache group ذات priority الأعلى. المجموعات الافتراضية تملك priority سالبة حتى تتمكن المجموعات المخصصة من أخذ أولوية أعلى، والقيمة الافتراضية للمجموعات المخصصة هي 0.
splitChunks.cacheGroups.{cacheGroup}.reuseExistingChunk
boolean = true
إذا كان chunk الحالي يحتوي على modules تم فصلها سابقًا من bundle الرئيسي، فسيُعاد استخدامه بدل إنشاء chunk جديد. قد يؤثر هذا في اسم ملف chunk الناتج.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
reuseExistingChunk: true,
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.type
function RegExp string
يسمح بإسناد modules إلى cache group حسب module type.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
json: {
type: "json",
},
},
},
},
};splitChunks.cacheGroups.test
splitChunks.cacheGroups.{cacheGroup}.test
function (module, { chunkGraph, moduleGraph }) => boolean RegExp string
يتحكم في modules التي تختارها هذه cache group. إذا حُذف، فسيختار كل modules. يمكنه مطابقة المسار المطلق للـ module resource أو أسماء chunks. عند مطابقة اسم chunk، يتم اختيار كل modules داخل ذلك chunk.
تمرير دالة إلى {cacheGroup}.test:
webpack.config.js
import path from "node:path";
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
svgGroup: {
test(module) {
// يحتوي `module.resource` على المسار المطلق للملف على القرص.
// لاحظ استخدام `path.sep` بدل / أو \ لضمان التوافق بين الأنظمة.
return (
module.resource &&
module.resource.endsWith(".svg") &&
module.resource.includes(`${path.sep}cacheable_svgs${path.sep}`)
);
},
},
byModuleTypeGroup: {
test(module) {
return module.type === "javascript/auto";
},
},
},
},
},
};لمعرفة المعلومات المتاحة داخل كائني module وchunks، يمكنك وضع عبارة debugger; داخل callback. ثم شغّل webpack build في debug mode لفحص parameters داخل Chromium DevTools.
تمرير RegExp إلى {cacheGroup}.test:
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
// لاحظ استخدام `[\\/]` كفاصل مسار لضمان التوافق بين الأنظمة.
test: /[\\/]node_modules[\\/]|vendor[\\/]analytics_provider|vendor[\\/]other_lib/,
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.filename
string function (pathData, assetInfo) => string
يسمح بتجاوز filename عندما يكون chunk أوليًا فقط.
كل placeholders المتاحة في output.filename متاحة هنا أيضًا.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
filename: "[name].bundle.js",
},
},
},
},
};وكدالة:
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
filename: (pathData) =>
// استخدم كائن pathData لتوليد filename بناءً على احتياجك
`${pathData.chunk.name}-bundle.js`,
},
},
},
},
};يمكن إنشاء بنية مجلدات عبر توفير prefix للمسار داخل filename، مثل: 'js/vendor/bundle.js'.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
filename: "js/[name]/bundle.js",
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.enforce
boolean = false
يخبر webpack بتجاهل خيارات splitChunks.minSize وsplitChunks.minChunks وsplitChunks.maxAsyncRequests وsplitChunks.maxInitialRequests، وإنشاء chunks دائمًا لهذه cache group.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
enforce: true,
},
},
},
},
};splitChunks.cacheGroups.{cacheGroup}.idHint
string
يضبط تلميح chunk id. سيُضاف إلى اسم ملف chunk.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
idHint: "vendors",
},
},
},
},
};أمثلة
القيم الافتراضية: المثال 1
// index.js
import("./a"); // dynamic import// a.js
import "react";
// ...النتيجة: سيتم إنشاء chunk منفصل يحتوي على react. عند استدعاء import، يتم تحميل هذا chunk بالتوازي مع chunk الأصلي الذي يحتوي على ./a.
السبب:
- الشرط 1: يحتوي chunk على modules من
node_modules. - الشرط 2: حجم
reactأكبر من 30kb. - الشرط 3: عدد الطلبات المتوازية عند استدعاء import هو 2.
- الشرط 4: لا يؤثر في الطلبات عند التحميل الأولي للصفحة.
ما المنطق خلف ذلك؟ غالبًا لن يتغير react بقدر تغيّر كود تطبيقك. بنقله إلى chunk منفصل، يمكن تخزين هذا chunk في cache بشكل منفصل عن كود تطبيقك، بشرط أنك تستخدم chunkhash أو records أو Cache-Control أو أي أسلوب long term cache آخر.
القيم الافتراضية: المثال 2
// entry.js
// dynamic imports
import("./a");
import("./b");// a.js
import "./helpers"; // حجم helpers هو 40kb
// ...// b.js
import "./helpers";
import "./more-helpers"; // حجم more-helpers أيضًا 40kb
// ...النتيجة: سيتم إنشاء chunk منفصل يحتوي على ./helpers وكل dependencies الخاصة به. عند استدعاءات import، يتم تحميل هذا chunk بالتوازي مع chunks الأصلية.
السبب:
- الشرط 1: chunk مشترك بين استدعائي import.
- الشرط 2: حجم
helpersأكبر من 30kb. - الشرط 3: عدد الطلبات المتوازية عند استدعاءات import هو 2.
- الشرط 4: لا يؤثر في الطلبات عند التحميل الأولي للصفحة.
وضع محتوى helpers داخل كل chunk سيؤدي إلى تنزيل كوده مرتين. باستخدام chunk منفصل، يحدث ذلك مرة واحدة فقط. نحن ندفع تكلفة طلب إضافي، وهذا يعد tradeoff. لذلك يوجد حد أدنى للحجم وهو 30kb.
Split Chunks: المثال 1
أنشئ chunk باسم commons يحتوي على كل الكود المشترك بين entry points.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: "commons",
chunks: "initial",
minChunks: 2,
},
},
},
},
};Split Chunks: المثال 2
أنشئ chunk باسم vendors يحتوي على كل الكود القادم من node_modules في التطبيق كاملًا.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all",
},
},
},
},
};Split Chunks: المثال 3
أنشئ chunk باسم custom vendor يحتوي على حزم معينة من node_modules تطابق RegExp.
webpack.config.js
export default {
// ...
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: "vendor",
chunks: "all",
},
},
},
},
};


