Hot Module Replacement

إذا تم تفعيل Hot Module Replacement عبر HotModuleReplacementPlugin، فستظهر واجهته تحت الخاصية module.hot، وكذلك تحت import.meta.webpackHot. لاحظ أن import.meta.webpackHot وحدها هي التي يمكن استخدامها داخل strict ESM.

عادةً يتحقق المستخدمون أولًا من توفر الواجهة، ثم يبدأون التعامل معها. هذا مثال على قبول تحديث module باستخدام accept:

if (module.hot) {
  module.hot.accept("./library.js", () => {
    // افعل شيئًا مع library module المحدّث...
  });
}

// أو
if (import.meta.webpackHot) {
  import.meta.webpackHot.accept("./library.js", () => {
    // افعل شيئًا مع library module المحدّث...
  });
}

الدوال التالية مدعومة.

Module API

accept

تقبل التحديثات للـ dependencies المحددة وتنفّذ callback للتعامل مع تلك التحديثات. ويمكنك أيضًا إضافة error handler اختياري:

module.hot.accept(
  dependencies, // إما string أو array من strings
  callback, // دالة تُنفّذ عند تحديث dependencies
  errorHandler, // (err, {moduleId, dependencyId}) => {}
);

// أو
import.meta.webpackHot.accept(
  dependencies, // إما string أو array من strings
  callback, // دالة تُنفّذ عند تحديث dependencies
  errorHandler, // (err, {moduleId, dependencyId}) => {}
);

عند استخدام import في ESM، يتم تحديث كل الرموز المستوردة من dependencies تلقائيًا. ملاحظة: يجب أن يطابق نص dependency النص الموجود بعد from في عبارة import بشكل كامل. وفي بعض الحالات يمكن حذف callback أصلًا. أما استخدام require() داخل callback فلا معنى له هنا.

عند استخدام CommonJS، يجب تحديث dependencies يدويًا باستخدام require() داخل callback. لذلك لا يكون حذف callback منطقيًا هنا.

errorHandler لـ accept

(err, {moduleId, dependencyId}) => {}

  • err: الخطأ الذي رماه callback في argument الثاني، أو الخطأ أثناء تنفيذ dependency عند استخدام ESM dependencies.
  • moduleId: معرّف module الحالي.
  • dependencyId: معرّف module الخاص بأول dependency تغيّر.

accept (self)

يقبل التحديثات الخاصة به.

module.hot.accept(
  errorHandler, // دالة لمعالجة الأخطاء عند تقييم الإصدار الجديد
);

// أو
import.meta.webpackHot.accept(
  errorHandler, // دالة لمعالجة الأخطاء عند تقييم الإصدار الجديد
);

عندما يتم تحديث هذا module أو dependencies الخاصة به، يمكن التخلص من هذا module وإعادة تقييمه بدون إبلاغ parents. يكون هذا منطقيًا إذا لم يكن لهذا module أي exports، أو إذا كانت exports تُحدّث بطريقة أخرى.

يتم تشغيل errorHandler عندما يرمي تقييم هذا module، أو dependencies الخاصة به، exception.

errorHandler لـ self accept

(err, {moduleId, module}) => {}

  • err: الخطأ الذي حدث عند تقييم الإصدار الجديد.
  • moduleId: معرّف module الحالي.
  • module: instance الحالية من module.
    • module.hot: يسمح باستخدام HMR API الخاصة بـ module instance التي حدث فيها الخطأ. سيناريو شائع هو عمل self accept لها مرة أخرى. ومن المفيد أيضًا إضافة dispose handler لتمرير البيانات. انتبه إلى أن module الذي حدث فيه الخطأ قد يكون نُفذ جزئيًا بالفعل، لذلك تأكد من عدم الدخول في حالة غير متسقة. يمكنك استخدام module.hot.data لتخزين حالة جزئية.
    • module.exports: يمكن استبداله، لكن كن حذرًا لأن أسماء الخصائص قد يتم تغييرها في production mode.

decline

يرفض التحديثات للـ dependencies المحددة، مما يجبر التحديث على الفشل بكود 'decline'.

module.hot.decline(
  dependencies, // إما string أو array من strings
);

// أو
import.meta.webpackHot.decline(
  dependencies, // إما string أو array من strings
);

يعلّم dependency على أنه غير قابل للتحديث. يكون هذا منطقيًا عندما لا يمكن التعامل مع تغيّر exports الخاصة بهذا dependency، أو عندما لم تُنفذ معالجة له بعد. بحسب كود إدارة HMR لديك، يؤدي تحديث هذه dependencies، أو dependencies غير المقبولة التابعة لها، غالبًا إلى full-reload للصفحة.

decline (self)

يرفض التحديثات الخاصة به.

module.hot.decline();

// أو
import.meta.webpackHot.decline();

يعلّم هذا module على أنه غير قابل للتحديث. يكون هذا منطقيًا عندما يملك module آثارًا جانبية لا يمكن عكسها، أو عندما لم تُنفذ معالجة HMR لهذا module بعد. بحسب كود إدارة HMR لديك، يؤدي تحديث هذا module، أو dependencies غير المقبولة، غالبًا إلى full-reload للصفحة.

dispose (or addDisposeHandler)

يضيف handler يُنفّذ عندما يتم استبدال كود module الحالي. استخدمه لإزالة أي resource دائم حجزته أو أنشأته. إذا أردت نقل state إلى module المحدّث، فأضفها إلى parameter المسمى data. سيكون هذا الكائن متاحًا في module.hot.data بعد التحديث.

module.hot.dispose((data) => {
  // نظّف الموارد ومرر البيانات إلى module المحدّث...
});

// أو
import.meta.webpackHot.dispose((data) => {
  // نظّف الموارد ومرر البيانات إلى module المحدّث...
});

invalidate

استدعاء هذه الدالة يُبطل module الحالي، فيتم التخلص منه وإعادة إنشائه عند تطبيق تحديث HMR. ويتصاعد هذا مثل أي تحديث عادي لهذا module. لا يستطيع module نفسه قبول invalidate عبر self-accept.

عند استدعائها أثناء حالة idle، سيتم إنشاء تحديث HMR جديد يحتوي على هذا module، وسينتقل HMR إلى حالة ready.

عند استدعائها أثناء حالتي ready أو prepare، سيُضاف هذا module إلى تحديث HMR الحالي.

عند استدعائها أثناء حالة check، سيُضاف هذا module إلى التحديث إذا كان هناك تحديث متاح. وإذا لم يكن هناك تحديث، فسيتم إنشاء تحديث جديد، وسينتقل HMR إلى حالة ready.

عند استدعائها أثناء حالتي dispose أو apply، سيأخذها HMR بعين الاعتبار بعد الخروج من تلك الحالات.

حالات الاستخدام

القبول الشرطي

يمكن لـ module قبول dependency، ثم استدعاء invalidate عندما يكون تغيّر dependency غير قابل للمعالجة:

import { processX, processY } from "anotherDep";
import { x, y } from "./dep";

const oldY = y;

processX(x);

export default processY(y);

module.hot.accept("./dep", () => {
  if (y !== oldY) {
    // لا يمكن التعامل مع هذا التغيير، ارفعه إلى parent
    module.hot.invalidate();
    return;
  }
  // يمكن التعامل مع هذا التغيير
  processX(x);
});

قبول self مشروط

يمكن لـ module أن يقبل تحديث نفسه، لكنه يستطيع إبطال نفسه عندما يكون التغيير غير قابل للمعالجة:

const VALUE = "constant";

export default VALUE;

if (
  module.hot.data &&
  module.hot.data.value &&
  module.hot.data.value !== VALUE
) {
  module.hot.invalidate();
} else {
  module.hot.dispose((data) => {
    data.value = VALUE;
  });
  module.hot.accept();
}

تشغيل تحديثات HMR مخصصة

const moduleId = chooseAModule();
const code = __webpack_modules__[moduleId].toString();
__webpack_modules__[moduleId] = eval(`(${makeChanges(code)})`);
if (require.cache[moduleId]) {
  require.cache[moduleId].hot.invalidate();
  module.hot.apply();
}

removeDisposeHandler

يزيل handler الذي تمت إضافته عبر dispose أو addDisposeHandler.

module.hot.removeDisposeHandler(callback);

// أو
import.meta.webpackHot.removeDisposeHandler(callback);

Management API

status

تجلب الحالة الحالية لعملية hot module replacement.

module.hot.status(); // سترجع إحدى النصوص التالية...

// أو
import.meta.webpackHot.status();
Statusالوصف
idleالعملية تنتظر استدعاء check
checkالعملية تفحص وجود تحديثات
prepareالعملية تستعد للتحديث، مثل تنزيل module المحدّث
readyالتحديث جاهز ومتاح
disposeالعملية تستدعي handlers الخاصة بـ dispose على modules التي سيتم استبدالها
applyالعملية تستدعي handlers الخاصة بـ accept وتعيد تنفيذ self-accepted modules
abortتم إلغاء التحديث، لكن النظام ما زال في حالته السابقة
failرمى التحديث exception وأصبحت حالة النظام غير موثوقة

check

يفحص كل modules المحملة بحثًا عن تحديثات، وإذا وُجدت تحديثات، يطبقها عبر apply.

module.hot
  .check(autoApply)
  .then((outdatedModules) => {
    // outdated modules...
  })
  .catch((error) => {
    // catch errors
  });

// أو
import.meta.webpackHot
  .check(autoApply)
  .then((outdatedModules) => {
    // outdated modules...
  })
  .catch((error) => {
    // catch errors
  });

يمكن أن يكون parameter المسمى autoApply إما boolean أو options تُمرر إلى الدالة apply عند استدعائها.

apply

تواصل عملية التحديث، طالما أن module.hot.status() === 'ready'.

module.hot
  .apply(options)
  .then((outdatedModules) => {
    // outdated modules...
  })
  .catch((error) => {
    // catch errors
  });

// أو
import.meta.webpackHot
  .apply(options)
  .then((outdatedModules) => {
    // outdated modules...
  })
  .catch((error) => {
    // catch errors
  });

يمكن أن يحتوي كائن options الاختياري على الخصائص التالية:

  • ignoreUnaccepted (boolean): تجاهل التغييرات التي حدثت في modules غير المقبولة.
  • ignoreDeclined (boolean): تجاهل التغييرات التي حدثت في modules المرفوضة.
  • ignoreErrored (boolean): تجاهل الأخطاء التي تُرمى داخل accept handlers أو error handlers أو أثناء إعادة تقييم module.
  • onDeclined (function(info)): notifier للـ modules المرفوضة.
  • onUnaccepted (function(info)): notifier للـ modules غير المقبولة.
  • onAccepted (function(info)): notifier للـ modules المقبولة.
  • onDisposed (function(info)): notifier للـ modules التي تم التخلص منها.
  • onErrored (function(info)): notifier للأخطاء.

سيكون parameter المسمى info كائنًا يحتوي على بعض القيم التالية:

{
  type: 'self-declined' | 'declined' |
        'unaccepted' | 'accepted' |
        'disposed' | 'accept-errored' |
        'self-accept-errored' | 'self-accept-error-handler-errored',
  moduleId: 4, // الـ module المعني.
  dependencyId: 3, // للأخطاء: معرّف module الذي يملك accept handler.
  chain: [1, 2, 3, 4], // للحالات declined/accepted/unaccepted: السلسلة التي انتشر منها التحديث.
  parentId: 5, // لـ declined: معرّف parent module الذي رفض التحديث
  outdatedModules: [1, 2, 3, 4], // لـ accepted: الـ modules القديمة التي سيتم التخلص منها
  outdatedDependencies: { // لـ accepted: مكان accept handlers التي ستتعامل مع التحديث
    5: [4]
  },
  error: new Error(...), // للأخطاء: الخطأ المرمي
  originalError: new Error(...) // لـ self-accept-error-handler-errored:
                                // الخطأ الذي رماه module قبل أن يحاول error handler التعامل معه.
}

addStatusHandler

يسجل دالة للاستماع إلى التغييرات في status.

module.hot.addStatusHandler((status) => {
  // تفاعل مع الحالة الحالية...
});

// أو
import.meta.webpackHot.addStatusHandler((status) => {
  // تفاعل مع الحالة الحالية...
});

ضع في بالك أنه عندما يرجع status handler كائن Promise، سينتظر نظام HMR حتى يتم حل Promise قبل المتابعة.

removeStatusHandler

يزيل status handler مسجلًا.

module.hot.removeStatusHandler(callback);

// أو
import.meta.webpackHot.removeStatusHandler(callback);
Edit this page·

1 Contributor

RlxChap2