واجهة Loader
الـ loader هو JavaScript module يصدّر دالة. يستدعي loader runner هذه الدالة ويمرر لها نتيجة loader السابق أو ملف resource. يملأ webpack وloader runner سياق this داخل الدالة بمجموعة دوال مفيدة تسمح للـ loader، من بين أشياء أخرى، بتغيير أسلوب الاستدعاء إلى async أو قراءة query parameters.
يُمرر إلى أول loader argument واحد: محتوى ملف resource. ويتوقع compiler نتيجة من آخر loader. يجب أن تكون النتيجة String أو Buffer، ويتم تحويل Buffer إلى string، وتمثل كود JavaScript المصدري للـ module. ويمكن أيضًا تمرير SourceMap اختياري ككائن JSON.
يمكن إرجاع نتيجة واحدة في الوضع المتزامن. أما النتائج المتعددة فتتطلب استدعاء this.callback()، ويجب أن يرجع loader القيمة undefined.
في الوضع غير المتزامن، يمكنك إرجاع نتيجة واحدة من async function. بدلًا من ذلك، يمكنك استدعاء this.async() للإشارة إلى أن loader runner يجب أن ينتظر نتيجة غير متزامنة. ترجع هذه الدالة this.callback(). في هذه الحالة يجب أن يرجع loader القيمة undefined ويستدعي ذلك callback. وهذا هو الخيار الوحيد عند وجود نتائج متعددة.
/**
*
* @param {string|Buffer} content Content of the resource file
* @param {object} [map] SourceMap data consumable by https://github.com/mozilla/source-map
* @param {any} [meta] Meta data, could be anything
*/
function webpackLoader(content, map, meta) {
// كود webpack loader الخاص بك
}أمثلة
تقدم الأقسام التالية أمثلة أساسية لأنواع loaders المختلفة. لاحظ أن parameters المسماة map وmeta اختيارية. راجع this.callback أدناه.
Synchronous Loaders
يمكن استخدام return أو this.callback لإرجاع content بعد تحويله بشكل متزامن:
sync-loader.js
export default function syncLoader(content, map, meta) {
return someSyncOperation(content);
}دالة this.callback أكثر مرونة لأنك تستطيع تمرير عدة arguments بدل استخدام content فقط.
sync-loader-with-multiple-results.js
export default function syncLoaderWithMultipleResults(content, map, meta) {
this.callback(null, someSyncOperation(content), map, meta);
return; // أرجع undefined دائمًا عند استدعاء callback()
}Asynchronous Loaders
بالنسبة للـ loaders غير المتزامنة، يمكنك إرجاع content بعد تحويله من async function:
async-loader.js
export default async function asyncLoader(content, map, meta) {
const result = await someAsyncOperation(content);
return result;
}أو يمكنك استخدام this.async للحصول على دالة callback:
async-loader-with-callback.js
export default function asyncLoaderWithCallback(content, map, meta) {
const callback = this.async();
someAsyncOperation(content, (err, result) => {
if (err) return callback(err);
callback(null, result, map, meta);
});
}async-loader-with-multiple-results.js
export default function asyncLoaderWithMultipleResults(content, map, meta) {
const callback = this.async();
someAsyncOperation(content, (err, result, sourceMaps, meta) => {
if (err) return callback(err);
callback(null, result, sourceMaps, meta);
});
}"Raw" Loader
افتراضيًا، يتم تحويل ملف resource إلى نص UTF-8 ثم تمريره إلى loader. عند ضبط flag المسمى raw على true، سيستقبل loader الـ Buffer الخام. يمكن لأي loader أن يعطي نتيجته كـ String أو Buffer. يحول compiler بينهما عند تمرير النتيجة بين loaders.
raw-loader.js
export default function rawLoader(content) {
assert(content instanceof Buffer);
return someSyncOperation(content);
// يمكن أن تكون قيمة الإرجاع `Buffer` أيضًا
// وهذا مسموح حتى لو لم يكن loader "raw"
}
export const raw = true;Pitching Loader
تُستدعى loaders دائمًا من اليمين إلى اليسار. توجد حالات يهتم فيها loader فقط بـ metadata وراء request ويمكنه تجاهل نتائج loader السابق. تُستدعى دالة pitch في loaders من اليسار إلى اليمين قبل تنفيذ loaders فعليًا من اليمين إلى اليسار.
في إعداد use التالي:
export default {
// ...
module: {
rules: [
{
// ...
use: ["a-loader", "b-loader", "c-loader"],
},
],
},
};ستحدث الخطوات التالية:
|- a-loader `pitch`
|- b-loader `pitch`
|- c-loader `pitch`
|- requested module is picked up as a dependency
|- c-loader normal execution
|- b-loader normal execution
|- a-loader normal executionلماذا قد يستفيد loader من مرحلة "pitching"؟
أولًا، data التي تُمرر إلى دالة pitch تكون متاحة أيضًا في مرحلة التنفيذ تحت this.data، وقد تفيد لالتقاط معلومات ومشاركتها من مرحلة مبكرة في الدورة.
export default function myLoaderName(content) {
return someSyncOperation(content, this.data.value);
}
export function pitch(remainingRequest, precedingRequest, data) {
data.value = 42;
}ثانيًا، إذا أعطى loader نتيجة داخل دالة pitch، تنعكس العملية ويتم تجاوز loaders المتبقية. في المثال السابق، إذا أرجعت دالة pitch الخاصة بـ b-loader شيئًا:
export default function myLoaderName(content) {
return someSyncOperation(content);
}
export function pitch(remainingRequest, precedingRequest, data) {
if (someCondition()) {
return `import _from_loader from "${JSON.stringify(`-!${remainingRequest}`)}"; export default _from_loader;`;
}
}ستُختصر الخطوات السابقة إلى:
|- a-loader `pitch`
|- b-loader `pitch` returns a module
|- a-loader normal executionسياق Loader
يمثل loader context الخصائص المتاحة داخل loader والمربوطة بالخاصية this.
Example for the loader context
في المثال التالي، يتم استخدام require call هذه:
داخل /abc/file.js:
import "./loader1?xyz!loader2!./resource?rrr";this.addContextDependency
addContextDependency(directory: string)يضيف مجلدًا كاعتماد لنتيجة loader.
this.addDependency
addDependency(file: string)
dependency(file: string) // اختصاريضيف ملفًا موجودًا كاعتماد لنتيجة loader حتى يصبح قابلًا للمراقبة. مثلًا، يستخدم sass-loader وless-loader ذلك لإعادة compilation كلما تغيّر أي ملف css مستورد.
this.addMissingDependency
addMissingDependency(file: string)يضيف ملفًا غير موجود كاعتماد لنتيجة loader حتى يصبح قابلًا للمراقبة. يشبه addDependency، لكنه يتعامل مع إنشاء الملفات أثناء compilation قبل إرفاق watchers بشكل صحيح.
this.async
يخبر loader-runner أن loader ينوي استدعاء callback بشكل غير متزامن. ترجع this.callback.
this.cacheable
دالة تضبط cacheable flag:
cacheable(flag = true: boolean)افتراضيًا، تُعلّم نتائج loader على أنها قابلة للتخزين المؤقت. استدعِ هذه الدالة مع false لجعل نتيجة loader غير قابلة للتخزين المؤقت.
يجب أن يعطي cacheable loader نتيجة deterministic عندما لا تتغير inputs وdependencies. هذا يعني أن loader لا ينبغي أن يملك dependencies غير تلك المحددة عبر this.addDependency.
this.callback
دالة يمكن استدعاؤها بشكل متزامن أو غير متزامن لإرجاع عدة نتائج. arguments المتوقعة هي:
this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any
);- يجب أن يكون argument الأول
Errorأوnull. - argument الثاني هو
stringأوBuffer. - اختياري: يجب أن يكون argument الثالث source map يمكن قراءتها بواسطة هذه الحزمة.
- اختياري: argument الرابع، الذي يتجاهله webpack، يمكن أن يكون أي شيء، مثل metadata.
إذا استُدعيت هذه الدالة، فيجب إرجاع undefined لتجنب نتائج loader الغامضة.
this.clearDependencies
clearDependencies();يزيل كل dependencies الخاصة بنتيجة loader، حتى dependencies الأولية وتلك الخاصة بـ loaders أخرى. فكر في استخدام pitch.
this.context
مجلد module. يمكن استخدامه كـ context لحل أشياء أخرى.
في المثال: /abc لأن resource.js موجود داخل هذا المجلد.
this.data
كائن بيانات مشترك بين مرحلة pitch والمرحلة العادية.
this.emitError
emitError(error: Error)يصدر خطأ يمكن عرضه أيضًا في output.
ERROR in ./src/lib.js (./src/loader.js!./src/lib.js)
Module Error (from ./src/loader.js):
Here is an Error!
@ ./src/index.js 1:0-25this.emitFile
emitFile(name: string, content: Buffer|string, sourceMap: {...})يصدر ملفًا. هذا خاص بـ webpack.
this.emitWarning
emitWarning(warning: Error)يصدر تحذيرًا سيظهر في output بالشكل التالي:
WARNING in ./src/lib.js (./src/loader.js!./src/lib.js)
Module Warning (from ./src/loader.js):
Here is a Warning!
@ ./src/index.js 1:0-25this.environment
يفحص نوع ميزات ES التي يمكن استخدامها في runtime-code المولّد.
مثال:
{
// تدعم البيئة arrow functions مثل '() => { ... }'
"arrowFunction": true,
// تدعم البيئة BigInt كقيمة literal مثل 123n
"bigIntLiteral": false,
// تدعم البيئة const وlet في تعريف المتغيرات
"const": true,
// تدعم البيئة destructuring مثل '{ a, b } = obj'
"destructuring": true,
// تدعم البيئة دالة import() غير متزامنة لاستيراد EcmaScript modules
"dynamicImport": false,
// تدعم البيئة import() غير متزامنة عند إنشاء worker، حاليًا لأهداف web فقط
"dynamicImportInWorker": false,
// تدعم البيئة for of مثل 'for (const x of array) { ... }'
"forOf": true,
// تدعم البيئة 'globalThis'
"globalThis": true,
// تدعم البيئة صيغة ECMAScript Module لاستيراد modules
"module": false,
// تدعم البيئة optional chaining مثل 'obj?.a' أو 'obj?.()'
"optionalChaining": true,
// تدعم البيئة template literals
"templateLiteral": true
}this.fs
وصول إلى خاصية inputFileSystem الخاصة بـ compilation.
this.getOptions(schema)
يستخرج خيارات loader المعطاة. ويمكنه اختياريًا استقبال JSON schema كـ argument.
this.getResolve
getResolve(options: ResolveOptions): resolve
resolve(context: string, request: string, callback: function(err, result: string))
resolve(context: string, request: string): Promise<string>ينشئ دالة resolve مشابهة لـ this.resolve.
يمكن استخدام أي خيارات تحت إعدادات webpack resolve. يتم دمجها مع خيارات resolve المعرّفة في الإعدادات. لاحظ أن "..." يمكن استخدامها داخل arrays لتمديد القيمة القادمة من خيارات resolve، مثل { extensions: [".sass", "..."] }.
options.dependencyType خيار إضافي. يسمح بتحديد نوع dependency، ويُستخدم لحل byDependency من خيارات resolve.
كل dependencies الخاصة بعملية resolving تُضاف تلقائيًا كـ dependencies إلى module الحالي.
this.hot
معلومات عن HMR للـ loaders.
export default function (source) {
console.log(this.hot); // true إذا كان HMR مفعّلًا عبر --hot flag أو إعدادات webpack
return source;
}this.hashDigest
string
الترميز المستخدم عند توليد hash. راجع output.hashDigest.
this.hashDigestLength
number
طول prefix الخاص بـ hash digest المراد استخدامه. راجع output.hashDigestLength.
this.hashFunction
string function
خوارزمية hashing المراد استخدامها. راجع output.hashFunction.
this.hashSalt
string
salt اختياري لتحديث hash عبر hash.update في Node.JS. راجع output.hashSalt.
this.importModule
5.32.0+this.importModule(request, options, [callback]): Promise
حل بديل خفيف للـ child compiler لتجميع request وتنفيذه وقت build.
request: نص request لتحميل module منه.options:layer: يحدد layer التي يوضع أو يُجمّع فيها هذا module.publicPath: public path المستخدم للـ modules المبنية.
callback: callback اختياري بأسلوب Node.js يرجع exports الخاصة بالـ module أو namespace object لـ ESM. سترجعimportModuleكائن Promise إذا لم يتم تمرير callback.
webpack.config.js
export default {
module: {
rules: [
{
test: /stylesheet\.js$/i,
use: ["./a-pitching-loader.js"],
type: "asset/source", // نضبط النوع على 'asset/source' لأن loader سيرجع string
},
],
},
};a-pitching-loader.js
export async function pitch(remaining) {
const result = await this.importModule(
`${this.resourcePath}.webpack[javascript/auto]!=!${remaining}`,
);
return result.default || result;
}src/stylesheet.js
import { green, red } from "./colors.js";
export default `body { background: ${red}; color: ${green}; }`;src/colors.js
export const red = "#f00";
export const green = "#0f0";src/index.js
import stylesheet from "./stylesheet.js";
// ستكون stylesheet نصًا `body { background: #f00; color: #0f0; }` وقت buildقد تلاحظ شيئًا في المثال السابق:
- لدينا pitching loader.
- نستخدم صيغة
!=!داخل pitching loader لضبط matchResource للـ request. أي سنستخدمthis.resourcePath + '.webpack[javascript/auto]'للمطابقة معmodule.rulesبدل resource الأصلي. .webpack[javascript/auto]امتداد شبه وهمي من نمط.webpack[type]. نستخدمه لتحديد module type افتراضي عندما لا يتم تحديد module type آخر. يُستخدم عادةً مع صيغة!=!.
لاحظ أن المثال السابق مبسط. يمكنك مراجعة المثال الكامل في مستودع webpack.
this.loaderIndex
الفهرس داخل array الخاصة بـ loaders للـ loader الحالي.
في المثال: في loader1 تكون 0، وفي loader2 تكون 1.
this.loadModule
loadModule(request: string, callback: function(err, source, sourceMap, module))يحل request المحدد إلى module، ويطبّق كل loaders المعدة، ثم يستدعي callback مع source المولّد وsourceMap وmodule instance، والتي تكون عادةً instance من NormalModule. استخدم هذه الدالة إذا كنت تحتاج معرفة كود المصدر الخاص بـ module آخر لتوليد النتيجة.
تستخدم this.loadModule داخل loader context قواعد resolve الخاصة بـ CommonJS افتراضيًا. استخدم this.getResolve مع dependencyType مناسب، مثل 'esm' أو 'commonjs' أو نوع مخصص، قبل استخدام semantics مختلفة.
this.loaders
array تحتوي على كل loaders. وهي قابلة للكتابة في مرحلة pitch.
loaders = [{request: string, path: string, query: string, module: function}]في المثال:
[
{
request: "/abc/loader1.js?xyz",
path: "/abc/loader1.js",
query: "?xyz",
module: [Function],
},
{
request: "/abc/node_modules/loader2/index.js",
path: "/abc/node_modules/loader2/index.js",
query: "",
module: [Function],
},
];this.mode
يقرأ mode الذي يعمل به webpack.
القيم الممكنة: 'production' و'development' و'none'.
this.query
- إذا كان loader معدًا بكائن
options، فستشير هذه الخاصية إلى ذلك الكائن. - إذا لم يكن لدى loader
options، لكنه استُدعي بـ query string، فستكون هذه الخاصية نصًا يبدأ بـ?.
this.request
نص request بعد حله.
في المثال: '/abc/loader1.js?xyz!/abc/node_modules/loader2/index.js!/abc/resource.js?rrr'
this.resolve
resolve(context: string, request: string, callback: function(err, result: string))يحل request مثل require expression.
- يجب أن يكون
contextمسارًا مطلقًا إلى مجلد. يُستخدم هذا المجلد كنقطة بداية للـ resolving. requestهو الطلب المراد حله. عادةً تكون الطلبات نسبية مثل./relativeأو module requests مثلmodule/path، لكن يمكن أيضًا استخدام مسارات مطلقة مثل/some/pathكـ requests.callbackدالة callback عادية بأسلوب Node.js تعطي المسار المحلول.
كل dependencies الخاصة بعملية resolving تُضاف تلقائيًا كـ dependencies إلى module الحالي.
this.resource
جزء resource من request، مع query.
في المثال: '/abc/resource.js?rrr'
this.resourcePath
ملف resource.
في المثال: '/abc/resource.js'
this.resourceQuery
query الخاصة بالـ resource.
في المثال: '?rrr'
this.rootContext
منذ webpack 4، أصبحت القيمة التي كانت سابقًا this.options.context متاحة باسم this.rootContext.
this.sourceMap
يخبرك هل يجب توليد source map أم لا. لأن توليد source maps قد يكون مكلفًا، يجب فحص هل هي مطلوبة فعلًا.
this.target
هدف compilation. يُمرر من خيارات الإعداد.
أمثلة للقيم: 'web' و'node'.
this.utils
5.27.0+وصول إلى الأدوات التالية.
absolutify: يرجع request string جديدًا باستخدام مسارات مطلقة عندما يكون ذلك ممكنًا.contextify: يرجع request string جديدًا يتجنب المسارات المطلقة عندما يكون ذلك ممكنًا.createHash: يرجع كائن Hash جديدًا من دالة hash المقدمة.
my-sync-loader.js
export default function (content) {
this.utils.contextify(
this.context,
this.utils.absolutify(this.context, "./index.js"),
);
this.utils.absolutify(this.context, this.resourcePath);
const mainHash = this.utils.createHash(
this._compilation.outputOptions.hashFunction,
);
mainHash.update(content);
mainHash.digest("hex");
// …
return content;
}this.version
إصدار Loader API. حاليًا 2. هذا مفيد لتوفير التوافق مع الإصدارات السابقة. باستخدام الإصدار يمكنك تحديد منطق مخصص أو fallbacks للتغييرات الكاسرة.
this.webpack
تكون هذه boolean مضبوطة على true عندما يتم التجميع بواسطة webpack.
خصائص خاصة بـ Webpack
توفر واجهة loader كل المعلومات المتعلقة بـ module. لكن في حالات نادرة قد تحتاج إلى الوصول إلى Compiler API نفسها.
لذلك لا تستخدمها إلا كحل أخير. استخدامها يقلل قابلية نقل loader.
this._compilation
وصول إلى كائن Compilation الحالي في webpack.
this._compiler
وصول إلى كائن Compiler الحالي في webpack.
خصائص context مهملة
this.debug
boolean flag. يتم ضبطه عند العمل في debug mode.
this.inputValue
تُمرر من آخر loader. إذا كنت ستنفذ input argument كـ module، ففكر في قراءة هذا المتغير كاختصار لأسباب أداء.
this.minimize
يخبرك هل يجب تصغير النتيجة أم لا.
this.value
يمرر قيمًا إلى loader التالي. إذا كنت تعرف ماذا ستصدّر النتيجة عند تنفيذها كـ module، فاضبط هذه القيمة هنا، كـ array تحتوي عنصرًا واحدًا فقط.
this._module
وصول غير رسمي إلى كائن Module الجاري تحميله.
الإبلاغ عن الأخطاء
يمكنك الإبلاغ عن الأخطاء من داخل loader عبر:
- استخدام this.emitError. سيبلّغ عن الأخطاء بدون إيقاف compilation الخاص بالـ module.
- استخدام
throwأو أي exception غير ممسوكة. رمي خطأ أثناء تشغيل loader سيؤدي إلى فشل compilation للـ module الحالي. - استخدام
callbackفي الوضع غير المتزامن. تمرير خطأ إلى callback سيؤدي أيضًا إلى فشل compilation للـ module.
مثال:
./src/index.js
import "./loader!./lib";رمي خطأ من loader:
./src/loader.js
export default function (source) {
throw new Error("This is a Fatal Error!");
}أو تمرير خطأ إلى callback في الوضع غير المتزامن:
./src/loader.js
export default function (source) {
const callback = this.async();
// ...
callback(new Error("This is a Fatal Error!"), source);
}سيُحزم module بهذا الشكل:
/***/ "./src/loader.js!./src/lib.js":
/*!************************************!*\
!*** ./src/loader.js!./src/lib.js ***!
\************************************/
/*! no static exports found */
/***/ (function(module, exports) {
throw new Error("Module build failed (from ./src/loader.js):\nError: This is a Fatal Error!\n at Object.module.exports (/workspace/src/loader.js:3:9)");
/***/ })ثم سيعرض build output الخطأ أيضًا، بشكل مشابه لـ this.emitError:
ERROR in ./src/lib.js (./src/loader.js!./src/lib.js)
Module build failed (from ./src/loader.js):
Error: This is a Fatal Error!
at Object.module.exports (/workspace/src/loader.js:2:9)
@ ./src/index.js 1:0-25كما ترى، لا تعرض الرسالة نص الخطأ فقط، بل تعرض أيضًا تفاصيل عن loader وmodule المعنيين:
- مسار module:
ERROR in ./src/lib.js - نص request:
(./src/loader.js!./src/lib.js) - مسار loader:
(from ./src/loader.js) - مسار المستدعي:
@ ./src/index.js 1:0-25
Inline matchResource
أُضيفت صيغة inline request جديدة في webpack v4. إضافة <match-resource>!=! قبل request ستضبط matchResource لذلك request.
عند ضبط matchResource، سيُستخدم للمطابقة مع module.rules بدل resource الأصلي. يفيد ذلك إذا كان يجب تطبيق loaders إضافية على resource، أو إذا كان نوع module يحتاج إلى تغيير. كما يظهر في stats ويُستخدم لمطابقة Rule.issuer وtest في splitChunks.
مثال:
file.js
/* STYLE: body { background: red; } */
console.log("yep");يمكن لـ loader تحويل الملف إلى الملف التالي واستخدام matchResource لتطبيق قواعد معالجة CSS التي حددها المستخدم:
file.js (تم تحويله بواسطة loader)
import "./file.js.css!=!extract-style-loader/getStyles!./file.js";
console.log("yep");سيضيف هذا dependency إلى extract-style-loader/getStyles!./file.js ويعامل النتيجة كأنها file.js.css. وبما أن module.rules لديها rule تطابق /\.css$/، فسيتم تطبيقها على هذا dependency.
قد يكون loader بهذا الشكل:
extract-style-loader/index.js
import getStylesLoader from "./getStyles";
export default function (source) {
if (STYLES_REGEXP.test(source)) {
source = source.replace(STYLES_REGEXP, "");
return `import ${JSON.stringify(
this.utils.contextify(
this.context || this.rootContext,
`${this.resource}.css!=!${getStylesLoader}!${this.remainingRequest}`,
),
)};${source}`;
}
return source;
}extract-style-loader/getStyles.js
export default function (source) {
const match = source.match(STYLES_REGEXP);
return match[0];
}Logging
واجهة Logging API متاحة منذ webpack 4.37. عند تفعيل logging في إعدادات stats، أو عند تفعيل infrastructure logging، يمكن لـ loaders تسجيل رسائل تُطبع بتنسيق logger المناسب، سواء stats أو infrastructure.
- يُفضّل أن تستخدم loaders الدالة
this.getLogger()للتسجيل، وهي اختصار لـcompilation.getLogger()مع مسار loader والملف المعالج. هذا النوع من logging يُخزن داخل Stats ويُنسق بناءً عليها. ويمكن لمستخدم webpack فلترته أو تصديره. - يمكن لـ loaders استخدام
this.getLogger('name')للحصول على logger مستقل باسم فرعي. ما زال مسار loader والملف المعالج يضافان. - يمكن لـ loaders استخدام fallback خاص لاكتشاف دعم logging، مثل
this.getLogger ? this.getLogger() : console، لتوفير fallback عند استخدام إصدار webpack قديم لا يدعم دالةgetLogger.



