واجهة 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
);
  1. يجب أن يكون argument الأول Error أو null.
  2. argument الثاني هو string أو Buffer.
  3. اختياري: يجب أن يكون argument الثالث source map يمكن قراءتها بواسطة هذه الحزمة.
  4. اختياري: 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-25

this.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-25

this.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

5.95.0+

الترميز المستخدم عند توليد hash. راجع output.hashDigest.

this.hashDigestLength

number

5.95.0+

طول prefix الخاص بـ hash digest المراد استخدامه. راجع output.hashDigestLength.

this.hashFunction

string function

5.95.0+

خوارزمية hashing المراد استخدامها. راجع output.hashFunction.

this.hashSalt

string

5.95.0+

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

قد تلاحظ شيئًا في المثال السابق:

  1. لدينا pitching loader.
  2. نستخدم صيغة !=! داخل pitching loader لضبط matchResource للـ request. أي سنستخدم this.resourcePath + '.webpack[javascript/auto]' للمطابقة مع module.rules بدل resource الأصلي.
  3. .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

  1. إذا كان loader معدًا بكائن options، فستشير هذه الخاصية إلى ذلك الكائن.
  2. إذا لم يكن لدى 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.
·تعديل هذه الصفحة
السابق ›
Hot Module Replacement
‹ التالي
واجهة Logger

1 مساهم

RlxChap2