React: كيف يمكننا إعداد تطبيقات HMR الآن بعد أن حل التحديث السريع محل أداة رد الفعل الساخنة؟

تم إنشاؤها على ٢٩ أغسطس ٢٠١٩  ·  85تعليقات  ·  مصدر: facebook/react

ذكر دان أبراموف أن Devtools v4 سيجعل react-hot-loader عفا عليه الزمن: https://twitter.com/dan_abramov/status/1144715740983046144؟s=20

أنا:
لدي هذا الخطاف:
require("react-reconciler")(hostConfig).injectIntoDevTools(opts);
لكن HMR تعمل دائمًا بدونها. هل هذا مطلب جديد الآن؟

دان:
نعم ، هذا ما تستخدمه الآلية الجديدة. الآلية الجديدة لا تحتاج إلى "رد فعل - أداة تحميل ساخنة" لذا بحلول الوقت الذي تقوم فيه بالتحديث ، قد ترغب في إزالة تلك الحزمة. (إنها غازية جدًا)

لا يمكنني رؤية أي ذكر لـ HMR في وثائق Devtools ، ومع ذلك ؛ الآن بعد أن أصبح react-hot-loader قديمًا (ومعه طريقة require("react-hot-loader/root").hot ) ، كيف يمكننا إعداد التطبيقات لـ HMR في:

  • رد على تطبيقات DOM
  • تفاعل مع التطبيقات الأصلية
  • تفاعل مع تطبيقات العارض المخصصة

سأكون مهتمًا بشكل خاص بدليل الترحيل المخصص لأي شخص قام بالفعل بإعداد HMR عبر react-hot-loader .

أيضًا ، بالنسبة إلى HMR ، هل يهم ما إذا كنا نستخدم Devtools المستقل أو ملحق المستعرض Devtools؟

Question

التعليق الأكثر فائدة

حسنًا ، هنا يذهب.

ما هو التحديث السريع؟

إنها إعادة تطبيق لـ "إعادة التحميل السريع" بدعم كامل من React. يتم شحنه في الأصل react-hot-loader ).

هل يمكنني استخدام التحديث السريع على الويب؟

من الناحية النظرية ، نعم ، هذه هي الخطة. عمليًا ، يحتاج شخص ما إلى دمجه مع الحزم الشائعة على الويب (مثل Webpack و Parcel). لم أتمكن من القيام بذلك حتى الآن. ربما شخص ما يريد أن يلتقطها. هذا التعليق هو دليل تقريبي لكيفية القيام بذلك.

مما تتكون؟

يعتمد التحديث السريع على عدة أجزاء تعمل معًا:

  • آلية "استبدال الوحدة الساخنة" في نظام الوحدة.

    • عادة ما يتم توفير ذلك أيضًا من قبل المجمع.

    • على سبيل المثال ، في حزمة الويب ، تتيح لك واجهة برمجة تطبيقات module.hot القيام بذلك.

  • React Renderer 16.9.0+ (مثل React DOM 16.9)
  • نقطة دخول react-refresh/runtime
  • البرنامج المساعد بابل react-refresh/babel

قد ترغب في العمل على جزء التكامل. أي دمج react-refresh/runtime مع آلية Webpack "استبدال الوحدة الساخنة".

كيف يبدو التكامل؟

⚠️⚠️⚠️ لكي تكون واضحًا ، هذا دليل للأشخاص الذين يرغبون في تنفيذ التكامل بأنفسهم. يتم إجراؤها على أهبة الاستعداد!

هناك بعض الأشياء التي تريد القيام بها في الحد الأدنى:

  • تمكين HMR في الحزمة الخاصة بك (مثل حزمة الويب)
  • تأكد من أن React هو 16.9.0+
  • أضف react-refresh/babel إلى ملحقات Babel الإضافية

في هذه المرحلة ، يجب أن يتعطل تطبيقك. يجب أن تحتوي على استدعاءات لوظائف $RefreshReg$ و $RefreshSig$ وهي وظائف غير محددة.

ثم تحتاج إلى إنشاء نقطة دخول JS جديدة والتي يجب تشغيلها قبل أي رمز في تطبيقك ، بما في ذلك react-dom (!) هذا مهم ؛ إذا تم تشغيله بعد react-dom ، فلن يعمل شيء. يجب أن تفعل نقطة الدخول شيئًا كالتالي:

if (process.env.NODE_ENV !== 'production' && typeof window !== 'undefined') {
  const runtime = require('react-refresh/runtime');
  runtime.injectIntoGlobalHook(window);
  window.$RefreshReg$ = () => {};
  window.$RefreshSig$ = () => type => type;
}

هذا يجب أن يصلح الأعطال. لكنها لا تزال غير قادرة على فعل أي شيء لأن عمليات التنفيذ هذه $RefreshReg$ و $RefreshSig$ لا يتم تنفيذها. إن ربطهم هو جوهر عمل التكامل الذي تحتاج إلى القيام به.

كيف تفعل ذلك يعتمد على الحزمة الخاصة بك. أفترض أنه باستخدام حزمة الويب ، يمكنك كتابة أداة تحميل تضيف بعض التعليمات البرمجية قبل وبعد تنفيذ كل وحدة. أو ربما يكون هناك بعض الخطاف لإدخال شيء ما في قالب الوحدة. بغض النظر ، ما تريد تحقيقه هو أن كل وحدة تبدو كالتالي:

// BEFORE EVERY MODULE EXECUTES

var prevRefreshReg = window.$RefreshReg$;
var prevRefreshSig = window.$RefreshSig$;
var RefreshRuntime = require('react-refresh/runtime');

window.$RefreshReg$ = (type, id) => {
  // Note module.id is webpack-specific, this may vary in other bundlers
  const fullId = module.id + ' ' + id;
  RefreshRuntime.register(type, fullId);
}
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;

try {

  // !!!
  // ...ACTUAL MODULE SOURCE CODE...
  // !!!

} finally {
  window.$RefreshReg$ = prevRefreshReg;
  window.$RefreshSig$ = prevRefreshSig;
}

الفكرة هنا هي أن المكون الإضافي Babel الخاص بنا يرسل استدعاءات لهذه الوظائف ، ومن ثم يربط تكاملنا أعلاه هذه المكالمات بمعرف الوحدة. بحيث يتلقى وقت التشغيل سلاسل مثل "path/to/Button.js Button" عند تسجيل أحد المكونات. (أو ، في حالة webpack ، ستكون المعرفات أرقامًا.) لا تنس أن كل من تحويل Babel وهذا الالتفاف يجب أن يحدث فقط في وضع التطوير.

كبديل لتغليف رمز الوحدة ، ربما توجد طريقة ما لإضافة محاولة / أخيرًا مثل هذا حول المكان الذي يقوم فيه المجمع بالفعل بتهيئة مصنع الوحدة. كما نفعل هنا في Metro (RN bundler). قد يكون هذا أفضل لأننا لن نحتاج إلى زيادة حجم كل وحدة ، أو القلق بشأن إدخال بناء جملة غير قانوني ، على سبيل المثال عند تغليف import بـ try / finally .

بمجرد ربط هذا ، لديك مشكلة أخيرة. لا تعلم أداة التجميع أنك تتعامل مع التحديثات ، لذا من المحتمل أن تعيد تحميل الصفحة على أي حال. عليك أن تقول لها لا تفعل. هذا مرة أخرى خاص بالمجمع ، ولكن الطريقة التي أقترحها هي التحقق مما إذا كانت جميع عمليات التصدير مكونات React ، وفي هذه الحالة ، "قبول" التحديث. في حزمة الويب ، يمكن أن يبدو الأمر كما يلي:


// ...ALL MODULE CODE...

const myExports = module.exports; 
// Note: I think with ES6 exports you might also have to look at .__proto__, at least in webpack

if (isReactRefreshBoundary(myExports)) {
  module.hot.accept(); // Depends on your bundler
  enqueueUpdate();
}

ما هو isReactRefreshBoundary ؟ إنه شيء يتم تعداده على الصادرات بشكل سطحي ويحدد ما إذا كان يصدر مكونات React فقط. هذه هي الطريقة التي تقرر بها قبول التحديث أم لا. لم أقم بنسخه ولصقه هنا ولكن هذا التنفيذ قد يكون بداية جيدة. (وفي هذا الرمز، Refresh يشير إلى react-refresh/runtime التصدير).

ستحتاج أيضًا إلى تسجيل جميع عمليات التصدير يدويًا لأن Babel convert سوف يستدعي فقط $RefreshReg$ للوظائف. إذا لم تفعل ذلك ، فلن يتم اكتشاف تحديثات للفصول الدراسية.

أخيرًا ، ستكون الوظيفة enqueueUpdate() شيئًا مشتركًا بين الوحدات التي تتخلى عن تحديث React الفعلي وتقوم به.

const runtime = require('react-refresh/runtime');

let enqueueUpdate = debounce(runtime.performReactRefresh, 30);

عند هذه النقطة يجب أن يكون لديك شيء يعمل.

الفروق الدقيقة

هناك بعض توقعات التجربة الأساسية التي أهتم بها والتي تدخل في العلامة التجارية "التحديث السريع". يجب أن يكون قادرًا على التعافي بأمان من خطأ في بناء الجملة أو خطأ في تهيئة الوحدة النمطية أو خطأ في العرض. لن أخوض في هذه الآليات بالتفصيل ، لكنني أشعر بقوة أنه لا يجب تسمية تجربتك "التحديث السريع" حتى تتعامل مع هذه الحالات جيدًا.

لسوء الحظ ، لا أعرف ما إذا كان webpack يمكنه دعم كل هؤلاء ، ولكن يمكننا طلب المساعدة إذا وصلت إلى حالة عمل إلى حد ما ولكن بعد ذلك تعثرت. على سبيل المثال ، لاحظت أن واجهة برمجة تطبيقات webpack accept() تجعل استرداد الأخطاء أكثر صعوبة (تحتاج إلى قبول الإصدار _السابق_ من الوحدة بعد حدوث خطأ) ، ولكن هناك طريقة للتغلب على ذلك. شيء آخر سنحتاج إلى الرجوع إليه هو "تسجيل" جميع الصادرات تلقائيًا ، وليس فقط تلك التي تم العثور عليها بواسطة المكون الإضافي Babel. في الوقت الحالي ، دعنا نتجاهل هذا ، ولكن إذا كان لديك شيء يعمل على سبيل المثال webpack ، فيمكنني النظر في تلميعه.

وبالمثل ، سنحتاج إلى دمجه مع تجربة "مربع الخطأ" ، على غرار تجربة react-error-overlay التي لدينا في إنشاء تطبيق React. هذا له بعض الفروق الدقيقة ، مثل يجب أن يختفي مربع الخطأ عند إصلاح خطأ. يتطلب ذلك أيضًا بعض العمل الإضافي الذي يمكننا القيام به بمجرد إنشاء المؤسسة.

اسمحوا لي أن أعرف إذا كان لديك أي أسئلة!

ال 85 كومينتر

هناك بعض الالتباس. لا تتيح DevTools الجديدة إعادة التحميل السريع (أو لها أي علاقة بإعادة التحميل). بدلاً من ذلك ، تستخدم تغييرات إعادة التحميل السريعة التي عمل دان عليها "الخطاف" الذي يستخدمه DevTools و React للتواصل. يضيف نفسه إلى الوسط حتى يتمكن من إعادة التحميل.

لقد قمت بتحرير العنوان لإزالة الإشارة إلى DevTools (لأنه قد يتسبب في حدوث ارتباك).

بالنسبة للسؤال حول كيفية استخدام HMR الجديد ، لا أعتقد أنني أعرف أحدث الأفكار هناك. أرى أن gaearon لديه علاقات عامة خاصة بالمسح في مستودع CRA
https://github.com/facebook/create-react-app/pull/5958

بالنسبة للسؤال حول كيفية استخدام HMR الجديد ، لا أعتقد أنني أعرف أحدث الأفكار هناك. أرى أن gaearon لديه علاقات عامة خاصة بالمسح في مستودع CRA

للتوضيح للقراء ، أن العلاقات العامة قديمة جدًا ولم تعد ذات صلة.


أحتاج إلى كتابة شيء ما حول كيفية عمل التحديث السريع وكيفية دمجه. لم يتح لها الوقت بعد.

حسنًا ، هنا يذهب.

ما هو التحديث السريع؟

إنها إعادة تطبيق لـ "إعادة التحميل السريع" بدعم كامل من React. يتم شحنه في الأصل react-hot-loader ).

هل يمكنني استخدام التحديث السريع على الويب؟

من الناحية النظرية ، نعم ، هذه هي الخطة. عمليًا ، يحتاج شخص ما إلى دمجه مع الحزم الشائعة على الويب (مثل Webpack و Parcel). لم أتمكن من القيام بذلك حتى الآن. ربما شخص ما يريد أن يلتقطها. هذا التعليق هو دليل تقريبي لكيفية القيام بذلك.

مما تتكون؟

يعتمد التحديث السريع على عدة أجزاء تعمل معًا:

  • آلية "استبدال الوحدة الساخنة" في نظام الوحدة.

    • عادة ما يتم توفير ذلك أيضًا من قبل المجمع.

    • على سبيل المثال ، في حزمة الويب ، تتيح لك واجهة برمجة تطبيقات module.hot القيام بذلك.

  • React Renderer 16.9.0+ (مثل React DOM 16.9)
  • نقطة دخول react-refresh/runtime
  • البرنامج المساعد بابل react-refresh/babel

قد ترغب في العمل على جزء التكامل. أي دمج react-refresh/runtime مع آلية Webpack "استبدال الوحدة الساخنة".

كيف يبدو التكامل؟

⚠️⚠️⚠️ لكي تكون واضحًا ، هذا دليل للأشخاص الذين يرغبون في تنفيذ التكامل بأنفسهم. يتم إجراؤها على أهبة الاستعداد!

هناك بعض الأشياء التي تريد القيام بها في الحد الأدنى:

  • تمكين HMR في الحزمة الخاصة بك (مثل حزمة الويب)
  • تأكد من أن React هو 16.9.0+
  • أضف react-refresh/babel إلى ملحقات Babel الإضافية

في هذه المرحلة ، يجب أن يتعطل تطبيقك. يجب أن تحتوي على استدعاءات لوظائف $RefreshReg$ و $RefreshSig$ وهي وظائف غير محددة.

ثم تحتاج إلى إنشاء نقطة دخول JS جديدة والتي يجب تشغيلها قبل أي رمز في تطبيقك ، بما في ذلك react-dom (!) هذا مهم ؛ إذا تم تشغيله بعد react-dom ، فلن يعمل شيء. يجب أن تفعل نقطة الدخول شيئًا كالتالي:

if (process.env.NODE_ENV !== 'production' && typeof window !== 'undefined') {
  const runtime = require('react-refresh/runtime');
  runtime.injectIntoGlobalHook(window);
  window.$RefreshReg$ = () => {};
  window.$RefreshSig$ = () => type => type;
}

هذا يجب أن يصلح الأعطال. لكنها لا تزال غير قادرة على فعل أي شيء لأن عمليات التنفيذ هذه $RefreshReg$ و $RefreshSig$ لا يتم تنفيذها. إن ربطهم هو جوهر عمل التكامل الذي تحتاج إلى القيام به.

كيف تفعل ذلك يعتمد على الحزمة الخاصة بك. أفترض أنه باستخدام حزمة الويب ، يمكنك كتابة أداة تحميل تضيف بعض التعليمات البرمجية قبل وبعد تنفيذ كل وحدة. أو ربما يكون هناك بعض الخطاف لإدخال شيء ما في قالب الوحدة. بغض النظر ، ما تريد تحقيقه هو أن كل وحدة تبدو كالتالي:

// BEFORE EVERY MODULE EXECUTES

var prevRefreshReg = window.$RefreshReg$;
var prevRefreshSig = window.$RefreshSig$;
var RefreshRuntime = require('react-refresh/runtime');

window.$RefreshReg$ = (type, id) => {
  // Note module.id is webpack-specific, this may vary in other bundlers
  const fullId = module.id + ' ' + id;
  RefreshRuntime.register(type, fullId);
}
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;

try {

  // !!!
  // ...ACTUAL MODULE SOURCE CODE...
  // !!!

} finally {
  window.$RefreshReg$ = prevRefreshReg;
  window.$RefreshSig$ = prevRefreshSig;
}

الفكرة هنا هي أن المكون الإضافي Babel الخاص بنا يرسل استدعاءات لهذه الوظائف ، ومن ثم يربط تكاملنا أعلاه هذه المكالمات بمعرف الوحدة. بحيث يتلقى وقت التشغيل سلاسل مثل "path/to/Button.js Button" عند تسجيل أحد المكونات. (أو ، في حالة webpack ، ستكون المعرفات أرقامًا.) لا تنس أن كل من تحويل Babel وهذا الالتفاف يجب أن يحدث فقط في وضع التطوير.

كبديل لتغليف رمز الوحدة ، ربما توجد طريقة ما لإضافة محاولة / أخيرًا مثل هذا حول المكان الذي يقوم فيه المجمع بالفعل بتهيئة مصنع الوحدة. كما نفعل هنا في Metro (RN bundler). قد يكون هذا أفضل لأننا لن نحتاج إلى زيادة حجم كل وحدة ، أو القلق بشأن إدخال بناء جملة غير قانوني ، على سبيل المثال عند تغليف import بـ try / finally .

بمجرد ربط هذا ، لديك مشكلة أخيرة. لا تعلم أداة التجميع أنك تتعامل مع التحديثات ، لذا من المحتمل أن تعيد تحميل الصفحة على أي حال. عليك أن تقول لها لا تفعل. هذا مرة أخرى خاص بالمجمع ، ولكن الطريقة التي أقترحها هي التحقق مما إذا كانت جميع عمليات التصدير مكونات React ، وفي هذه الحالة ، "قبول" التحديث. في حزمة الويب ، يمكن أن يبدو الأمر كما يلي:


// ...ALL MODULE CODE...

const myExports = module.exports; 
// Note: I think with ES6 exports you might also have to look at .__proto__, at least in webpack

if (isReactRefreshBoundary(myExports)) {
  module.hot.accept(); // Depends on your bundler
  enqueueUpdate();
}

ما هو isReactRefreshBoundary ؟ إنه شيء يتم تعداده على الصادرات بشكل سطحي ويحدد ما إذا كان يصدر مكونات React فقط. هذه هي الطريقة التي تقرر بها قبول التحديث أم لا. لم أقم بنسخه ولصقه هنا ولكن هذا التنفيذ قد يكون بداية جيدة. (وفي هذا الرمز، Refresh يشير إلى react-refresh/runtime التصدير).

ستحتاج أيضًا إلى تسجيل جميع عمليات التصدير يدويًا لأن Babel convert سوف يستدعي فقط $RefreshReg$ للوظائف. إذا لم تفعل ذلك ، فلن يتم اكتشاف تحديثات للفصول الدراسية.

أخيرًا ، ستكون الوظيفة enqueueUpdate() شيئًا مشتركًا بين الوحدات التي تتخلى عن تحديث React الفعلي وتقوم به.

const runtime = require('react-refresh/runtime');

let enqueueUpdate = debounce(runtime.performReactRefresh, 30);

عند هذه النقطة يجب أن يكون لديك شيء يعمل.

الفروق الدقيقة

هناك بعض توقعات التجربة الأساسية التي أهتم بها والتي تدخل في العلامة التجارية "التحديث السريع". يجب أن يكون قادرًا على التعافي بأمان من خطأ في بناء الجملة أو خطأ في تهيئة الوحدة النمطية أو خطأ في العرض. لن أخوض في هذه الآليات بالتفصيل ، لكنني أشعر بقوة أنه لا يجب تسمية تجربتك "التحديث السريع" حتى تتعامل مع هذه الحالات جيدًا.

لسوء الحظ ، لا أعرف ما إذا كان webpack يمكنه دعم كل هؤلاء ، ولكن يمكننا طلب المساعدة إذا وصلت إلى حالة عمل إلى حد ما ولكن بعد ذلك تعثرت. على سبيل المثال ، لاحظت أن واجهة برمجة تطبيقات webpack accept() تجعل استرداد الأخطاء أكثر صعوبة (تحتاج إلى قبول الإصدار _السابق_ من الوحدة بعد حدوث خطأ) ، ولكن هناك طريقة للتغلب على ذلك. شيء آخر سنحتاج إلى الرجوع إليه هو "تسجيل" جميع الصادرات تلقائيًا ، وليس فقط تلك التي تم العثور عليها بواسطة المكون الإضافي Babel. في الوقت الحالي ، دعنا نتجاهل هذا ، ولكن إذا كان لديك شيء يعمل على سبيل المثال webpack ، فيمكنني النظر في تلميعه.

وبالمثل ، سنحتاج إلى دمجه مع تجربة "مربع الخطأ" ، على غرار تجربة react-error-overlay التي لدينا في إنشاء تطبيق React. هذا له بعض الفروق الدقيقة ، مثل يجب أن يختفي مربع الخطأ عند إصلاح خطأ. يتطلب ذلك أيضًا بعض العمل الإضافي الذي يمكننا القيام به بمجرد إنشاء المؤسسة.

اسمحوا لي أن أعرف إذا كان لديك أي أسئلة!

يجب أن تكون الأخطاء النحوية / أخطاء التهيئة "سهلة بدرجة كافية" للتعامل معها بطريقة ما قبل إخبار React ببدء تصيير ، ولكن كيف يمكن أن تتفاعل أخطاء العرض مع حدود الخطأ؟

إذا حدث خطأ في العرض ، فسيؤدي ذلك إلى تشغيل أقرب حد للخطأ والذي سينتقل إلى حالة خطأ ، ولا توجد طريقة عامة لإخبار حدود الخطأ بأن أطفالهم _ ربما _ تم إصلاحهم بطريقة سحرية بعد التحديث المباشر. هل / هل يجب أن يحصل كل مكون قابل للتحديث على حدود الخطأ الخاصة به مجانًا ، أم أن معالجة الأخطاء تعمل بشكل مختلف في المصلح عندما يتم اكتشاف دعم وقت التشغيل عند التهيئة؟

جميع عمليات التصدير هي مكونات React ، وفي هذه الحالة ، "اقبل" التحديث

هل هناك طريقة للكشف عن هذه المكونات؟ بقدر ما أفهم - لا. باستثناء export.toString().indexOf('React')>0 ، لكنه سيتوقف عن العمل مع أي HOC مطبق.
بالإضافة إلى _القبول الذاتي_ في نهاية الملف ليس عرضة للخطأ - لن يتم إنشاء مقبض القبول الجديد ، وسوف ينتقل التحديث التالي إلى الحد الأعلى ، ولهذا السبب تم إنشاء require("react-hot-loader/root").hot .

على أي حال - يبدو أنه إذا قام أحدهم بإلقاء كل التعليمات البرمجية الخاصة بالتفاعل من react-hot-loader ، مع الحفاظ على واجهة برمجة التطبيقات الخارجية كما هي - فسيكون ذلك كافيًا وقابل للتطبيق على جميع التثبيتات الحالية.

استخدام react-refresh/babel 0.4.0 يعطيني هذا الخطأ على عدد كبير من الملفات:

ERROR in ../orbit-app/src/hooks/useStores.ts
Module build failed (from ../node_modules/babel-loader/lib/index.js):
TypeError: Cannot read property '0' of undefined
    at Function.get (/Users/nw/projects/motion/orbit/node_modules/@babel/traverse/lib/path/index.js:115:33)
    at NodePath.unshiftContainer (/Users/nw/projects/motion/orbit/node_modules/@babel/traverse/lib/path/modification.js:191:31)
    at PluginPass.exit (/Users/nw/projects/motion/orbit/node_modules/react-refresh/cjs/react-refresh-babel.development.js:546:28)

لقد قمت بتضييق نطاق هذا الملف إلى أبسط ما يسببه:

import { useContext } from 'react'

export default () => useContext()

إذا حدث خطأ في العرض ، فسيؤدي ذلك إلى تشغيل أقرب حد للخطأ والذي سينتقل إلى حالة خطأ ، ولا توجد طريقة عامة لإخبار حدود الخطأ بأن أطفالهم ربما تم إصلاحهم بطريقة سحرية بعد التحديث المباشر.

تتذكر شفرة التحديث السريع داخل React الحدود الفاشلة حاليًا. متى تمت جدولة تحديث Fast Refresh ، فسيتم إعادة تحميله دائمًا.

إذا لم تكن هناك حدود ، ولكن فشل الجذر عند التحديث ، فإن التحديث السريع سيعيد محاولة عرض هذا الجذر مع آخر عنصر له.

إذا فشل الجذر في التثبيت ، فسيخبرك runtime.hasUnrecoverableErrors() بذلك. ثم عليك أن تفرض إعادة التحميل. يمكننا التعامل مع هذه الحالة لاحقًا ، لم يكن لدي الوقت لإصلاحها بعد.

يعطيني استخدام رد الفعل-التحديث / بابل 0.4.0 هذا الخطأ في عدد كبير من الملفات:

تقديم قضية جديدة من فضلك؟

هل هناك طريقة للكشف عن هذه المكونات؟

لقد ربطت التطبيق الخاص بي ، والذي يستخدم في حد ذاته Runtime.isLikelyAReactComponent() . إنها ليست مثالية ولكنها جيدة بما يكفي.

لن يتم إنشاء مقبض القبول الجديد ، وسوف ينتقل التحديث التالي إلى الحد الأعلى

هل يمكنك صنع مثال؟ أنا لا تتبع. بغض النظر ، هذا شيء خاص بالمجمع. لقد جعلت مترو يفعل ما أريد. يمكننا أن نطلب من حزمة الويب إضافة شيء ما إذا فقدنا واجهة برمجة تطبيقات.

الهدف هنا هو إعادة تنفيذ أقل عدد ممكن من الوحدات مع ضمان الاتساق. لا نريد فقاعات تحديثات على الجذر لمعظم التعديلات.

يبدو أنه إذا قام أحدهم بإلقاء كل التعليمات البرمجية الخاصة بالتفاعل من أداة تحميل التفاعل الساخن ، مع الحفاظ على واجهة برمجة التطبيقات الخارجية كما هي

ربما ، على الرغم من أنني أرغب في إزالة حاوية المستوى العلوي أيضًا. أريد أيضًا تكاملًا أكثر إحكامًا مع مربع الخطأ. ربما لا يزال من الممكن أن يسمى هذا react-hot-loader .

بالمناسبة قمت بتحرير دليلي لتضمين قطعة مفقودة نسيت - المكالمة performReactRefresh . هذا هو الشيء الذي يقوم بجدولة التحديثات بالفعل.

isLikelyComponentType(type) {
   return typeof type === 'function' && /^[A-Z]/.test(type.name);
},

لن أشعر بالأمان مع هذا المنطق. حتى لو كانت جميع الدوال الكبيرة ذات الأحرف الكبيرة عبارة عن مكونات تفاعلية دائمًا تقريبًا - فإن العديد من الوحدات النمطية (الخاصة بي) لها صادرات أخرى أيضًا. على سبيل المثال _exports-for-tests_. هذه ليست مشكلة ، ولكنها تخلق بعض عدم القدرة على التنبؤ - يمكن إنشاء حدود ساخنة في أي وقت ... أو لا يتم إنشاؤها بعد تغيير سطر واحد.
ما الذي يمكن أن يكسر اختبار isLikelyComponentType :

  • تم تصدير mapStateToProps (للاختبارات ، ولم يتم استخدامها في كود الإنتاج)
  • تم تصدير hook (وهذا جيد)
  • تم تصدير Class والذي قد لا يكون فئة تفاعل (لن ، ولكن يجب)

لذلك - ستكون هناك حالات يتم فيها إنشاء حدود ساخنة ، لكنها لن تكون كذلك ، وستكون هناك حالات يتم فيها إنشاء حدود ساخنة ، ولكن لا يتم ذلك. يبدو وكأنه إعادة تحميل ساخنة قديمة غير مستقرة ، كلانا لا يحبذ تمامًا :)

هناك مكان واحد لا يمكن فيه توقع تطبيق الحدود الساخنة ، وسيكون متوقعًا تمامًا - حد thing أو domain ، أو فهرس دليل ، أي إعادة تصدير index.js "واجهة برمجة تطبيقات عامة" من Component.js في نفس الدليل (وليس نمط Facebook afaik).

بمعنى آخر - كل ما فعلته في المترو ، ولكن مع تطبيق المزيد من القيود. كل شيء آخر ، مثل قاعدة الفحص لإنشاء مثل هذه الحدود لأي مكون تم تحميله كسول ، يمكن استخدامه لفرض السلوك الصحيح.

بالحديث عن أي - التحديث السريع الساخن سيتعامل مع الكسل؟ هل من المتوقع أن يكون لها حد من الجانب الآخر من import ؟

لقد حاولت سريعًا أن ترى السحر في المتصفح وهو أمر رائع جدًا :) فعلت أبسط شيء ممكن ، أي ترميز جميع أكواد الأجهزة ، لذلك لا توجد مكونات webpack الإضافية هناك حتى الآن

Kapture 2019-09-07 at 23 09 04

الريبو هنا: https://github.com/pekala/react-refresh-test

مجرد فضول ولكن بالنسبة لـ webpack ، ألا يمكنك الحصول على مكون إضافي من babel لتصفية المحاولة / أخيرًا؟ أريد فقط أن أتأكد من أنني لا أفتقد شيئًا قبل أن أعطيه فرصة.

المكون الإضافي Babel ليس خاصًا بالبيئة. أود الاحتفاظ بها على هذا النحو. لا يعرف أي شيء عن الوحدات النمطية أو تحديث آلية الانتشار. تلك تختلف تبعا للحزمة.

على سبيل المثال في Metro ، لا توجد محاولة / التفاف التحويل أخيرًا على الإطلاق. بدلاً من ذلك ، أضع try / أخيرًا في وقت تشغيل التجميع نفسه حول المكان الذي يستدعي فيه مصنع الوحدة. سيكون ذلك مثاليًا مع حزمة الويب أيضًا ، لكنني لا أعرف ما إذا كان يتيح لك الاتصال بوقت التشغيل من هذا القبيل.

يمكنك بالطبع إنشاء ملحق Babel آخر للالتفاف. لكن هذا لا يشتري لك أي شيء مقابل القيام بذلك عبر حزمة الويب. نظرًا لأنها خاصة بحزمة الويب على أي حال. وقد يكون محيرًا أنك قد تقوم عن طريق الخطأ بتشغيل المكون الإضافي Babel في بيئة أخرى (وليس webpack) حيث لن يكون ذلك منطقيًا.

يمكنك ، عن طريق التثبيت في خطاف الشلال compilation.mainTemplate.hooks.require . الاستدعاء السابق لها هو النص الافتراضي لوظيفة __webpack_require__ ، لذا يمكنك الضغط على الخطاف لف المحتويات في كتلة try/finally .

تكمن المشكلة في الحصول على مرجع إلى React داخل __webpack_require__ . هذا ممكن ، لكنه قد يتطلب درجة معينة من إعادة الدخول والحراس العوديين.

لمزيد من التفاصيل ، حدد MainTemplate.js و web/JsonpMainTemplatePlugin.js في شفرة مصدر webpack. JsonpMainTemplatePlugin نفسه ينقر على مجموعة من الخطافات من MainTemplate.js لذلك ربما يكون هذا هو "اللحم" الذي تحتاج إلى معالجته.

إليكم نموذجًا أوليًا مبتذلًا اخترقته معًا ويفعل بشكل فعال ما أوضحه دان أعلاه. إنه غير مكتمل بشكل محزن ، لكنه يثبت تطبيق lo-fi في حزمة الويب: https://gist.github.com/maisano/441a4bc6b2954205803d68deac04a716

بعض الملاحظات:

  • react-dom مرمز هنا ، لذا لن يعمل هذا مع العارضين المخصصين أو الحزم الفرعية (مثل react-dom/profiling ).
  • لم أتعمق كثيرًا في كيفية عمل جميع متغيرات قوالب webpack ، لكن الطريقة التي قمت بها بتغليف تنفيذ الوحدة هي عملية معقدة للغاية. لست متأكدًا مما إذا كان هذا المثال سيعمل ، على سبيل المثال ، إذا كان الشخص يستخدم هدف مكتبة umd .

تكمن المشكلة في الحصول على مرجع إلى React داخل __webpack_require__. هذا ممكن ، لكنه قد يتطلب درجة معينة من إعادة الدخول والحراس العوديين.

أفترض أنك تقصد الحصول على إشارة إلى Refresh Runtime.

في Metro ، قمت بحل هذا عن طريق القيام بـ require.Refresh = RefreshRuntime أقرب وقت ممكن. ثم داخل تطبيق require يمكنني قراءة خاصية من وظيفة require نفسها. لن يكون متاحًا على الفور ولكن لا يهم إذا قمنا بتعيينه في وقت مبكر بما فيه الكفاية.

maisano اضطررت إلى تغيير عدد من الأشياء ، وفي النهاية لا أرى وظيفة القبول التي يطلق عليها webpack. لقد جربت كلاً من .accept(module.i, () => {}) و .accept(() => {}) (قبول ذاتي ، باستثناء أن هذا لا يعمل في حزمة الويب). الخاصية hot ممكّنة ، أرى أنها تنزل وتعمل من خلال الوحدات المقبولة.

لذلك انتهيت من تصحيح حزمة الويب لاستدعاء وحدات القبول الذاتي ، وكان هذا هو الإصلاح النهائي.

هنا التصحيح:

diff --git a/node_modules/webpack/lib/HotModuleReplacement.runtime.js b/node_modules/webpack/lib/HotModuleReplacement.runtime.js
index 5756623..7e0c681 100644
--- a/node_modules/webpack/lib/HotModuleReplacement.runtime.js
+++ b/node_modules/webpack/lib/HotModuleReplacement.runtime.js
@@ -301,7 +301,10 @@ module.exports = function() {
                var moduleId = queueItem.id;
                var chain = queueItem.chain;
                module = installedModules[moduleId];
-               if (!module || module.hot._selfAccepted) continue;
+               if (!module || module.hot._selfAccepted) {
+                   module && module.hot._selfAccepted()
+                   continue;
+               }
                if (module.hot._selfDeclined) {
                    return {
                        type: "self-declined",

أعلم أن هذا يتعارض مع واجهة برمجة التطبيقات (API) الخاصة بهم ، والتي تريد أن يكون ذلك "خطأ اتصال" ، أتذكر أنني واجهت هذا منذ سنوات عديدة على وجه التحديد أثناء العمل على HMR الداخلي ، وفي النهاية انتهى بنا الأمر بكتابة التجميع الخاص بنا. أعتقد أن الطرد يدعم واجهة برمجة تطبيقات رد الاتصال "ذات القبول الذاتي". ربما يستحق الأمر فتح مشكلة على webpack ومعرفة ما إذا كان بإمكاننا دمجها؟ sokra

لذا ... لقد صقلت المكون الإضافي بناءً على عمل maisano :
https://github.com/pmmmwh/react-refresh-webpack-plugin
(لقد كتبته في TypeScript لأنني لا أثق بنفسي في العبث بأجزاء webpack الداخلية عندما بدأت ، يمكنني تحويل ذلك إلى JS / Flow عادي)

حاولت إزالة الحاجة إلى أداة تحميل لحقن كود الوحدة الساخنة مع فئات webpack Dependency ، ولكن يبدو أن ذلك سيتطلب إعادة تحليل لجميع الوحدات (لأنه حتى مع جميع الوظائف المضمنة ، ما زلنا بحاجة إلى إشارة إلى react-refresh/runtime في مكان ما).

ثمة مسألة أخرى هي أنه لا توجد طرق بسيطة (AFAIK) للكشف عن جافا سكريبت مثل الملفات في webpack - على سبيل المثال html-webpack-plugin يستخدم javascript/auto اكتب كذلك، لذلك أنا من الصعب ترميز ما يبدو قناع ملف مقبول (JS / TS / Flow) لحقن اللودر.

أضفت أيضًا استرداد الخطأ (على الأقل خطأ في بناء الجملة) بناءً على تعليق من gaearon في هذا الموضوع البالغ من العمر 5 سنوات . التالي هو التعافي من أخطاء التفاعل - أظن أنه يمكن القيام بذلك عن طريق إدخال حد خطأ عالمي (مثل AppWrapper من react-hot-loader ) ، والذي سيتعامل أيضًا مع واجهة مربع الخطأ ، لكنه لم يفعل لديك الوقت للوصول إلى ذلك تمامًا حتى الآن.

يتم أيضًا تجنب المشكلة التي أثارتها natew - تم تحقيقها من خلال فصل المكالمة enqueueUpdate والمكالمة hot.accpet(errorHandler) .

pmmmwh ما هو التوقيت! لقد قمت للتو بإنشاء الريبو الذي بني على / تعديل القليل من العمل الذي قمت بمشاركته في الجوهر.

لم أقم بمعالجة الأخطاء بأي حال من الأحوال ، على الرغم من أن المكون الإضافي هنا أقوى قليلاً من النهج الأولي الذي اتبعته.

التالي هو التعافي من أخطاء التفاعل - أظن أنه يمكن القيام بذلك عن طريق حقن حد خطأ عالمي (نوعًا ما مثل AppWrapper من أداة تحميل التفاعل الساخن) ، والتي ستعالج أيضًا واجهة مربع الخطأ ، ولكن لم يكن لديك الوقت للوصول إلى هذا تمامًا حتى الآن.

يجب أن يعمل هذا بالفعل خارج الصندوق. لا حاجة إلى حد خطأ مخصص أو التفاف هنا.

التالي هو التعافي من أخطاء التفاعل - أظن أنه يمكن القيام بذلك عن طريق حقن حد خطأ عالمي (نوعًا ما مثل AppWrapper من أداة تحميل التفاعل الساخن) ، والتي ستعالج أيضًا واجهة مربع الخطأ ، ولكن لم يكن لديك الوقت للوصول إلى هذا تمامًا حتى الآن.

يجب أن يعمل هذا بالفعل خارج الصندوق. لا حاجة إلى حد خطأ مخصص أو التفاف هنا.

تضمين التغريدة لقد حاولت إلقاء أخطاء في عرض مكونات وظيفة - إذا حدث الخطأ في return ، فإن HMR يعمل ، ولكن إذا حدث في مكان آخر ، فلن يعمل في بعض الأحيان.

pmmmwh ما هو التوقيت! لقد قمت للتو بإنشاء الريبو الذي بني على / تعديل القليل من العمل الذي قمت بمشاركته في الجوهر.

لم أقم بمعالجة الأخطاء بأي حال من الأحوال ، على الرغم من أن المكون الإضافي هنا أقوى قليلاً من النهج الأولي الذي اتبعته.

maisano ماذا يجب أن أقول؟ لقد بدأت بالفعل في العمل على هذا الأمر وتورطت في مشكلة حقن التبعية في نهاية الأسبوع الماضي ... لقد وفرت لي فكرة المخرج: تادا:

إذا حدث الخطأ في المقابل ، يعمل HMR ، ولكن إذا حدث في مكان آخر ، فلن يعمل في بعض الأحيان.

سأحتاج إلى مزيد من التفاصيل حول ما جربته بالضبط وما تعنيه بكلمة "يعمل" و "لا يعمل".

هناك العديد من الأشياء التي يمكن أن تسوء إذا لم يتم تنفيذ تكامل مجمع الوحدات بشكل صحيح (وهو الموضوع أو هذا الموضوع). أتوقع أنه لا يوجد شيء في React نفسها يمنع الاسترداد من الأخطاء التي تحدث أثناء عمليات التحرير. يمكنك التحقق من أنه يعمل في React Native 0.61 RC3.

pmmmwh،maisano على الاختيار التالية يتخطى الوحدات مع المكونات كما صادرات اسمه ويتم تأسيس أي حدود التحديث:

https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/master/src/loader/utils/isReactRefreshBoundary.ts#L23 -L27

const desc = Object.getOwnPropertyDescriptor(moduleExports, key);
if (desc && desc.get) {
  // Don't invoke getters as they may have side effects.
  return false;
}

لا أعرف سبب الحاجة إلى هذا في Metro ، ولكن في webpack تقوم الحاصلات بإرجاع الصادرات المحددة وبقدر ما أستطيع أن أقول أنه لا توجد آثار جانبية. لذلك يجب أن تكون آمنة للإزالة.

gaearon React.lazy لم يتم إعادة تصيير المكونات (على سبيل المثال ، مسارات تقسيم الكود). هل هذا حسب التصميم؟ أستطيع أن أرى أن حد التحديث قد تم إنشاؤه ولكن يبدو أن performReactRefresh() لا يفعل شيئًا. يتم تحديث التغييرات على الأطفال الكسالى بشكل جيد ، لذلك هذه ليست مشكلة كبيرة ، لكنني أتساءل عما إذا كنا نفعل شيئًا خاطئًا ...

lazy عبارة عن آلة حالة صغيرة - تحتوي على مرجع للمكوِّن القديم ، ويجب تحديث هذا المرجع.
الآن دعنا نتخيل أنه كان كذلك ، والآن نشير إلى كائن جديد تمامًا lazy ) - سيتعين عليه التفكير في المرحلة loading مرة أخرى ، ومن المحتمل أن يؤدي ذلك إلى تدمير كل الشجرة المتداخلة.

أتوقع كسول للعمل. ربما كسر شيء ما. أحتاج إلى رؤية حالة استنساخ.

نظرًا لوجود عدد قليل من النماذج الأولية ، فهل يجب أن نختار واحدة ثم ننقل هذه المناقشة إلى قضاياها؟ وتكرر هناك.

هناك:
https://github.com/maisano/react-refresh-plugin
و:
https://github.com/pmmmwh/react-refresh-webpack-plugin
لقد قمت بإعداد تفرع من المكون الإضافي pmmmwh الذي يعمل مع [email protected] (أيضًا إصلاحات الصادرات المسماة):
https://github.com/WebHotelier/webpack-fast-refresh

ماذا عن react-hot-loader ؟

react-hot-loader بنقل جميع الميزات تقريبًا بدءًا من fast refresh ، ولكن هناك القليل من اللحظات التاريخية والتكاملية ، والتي لا تسمح بالحفاظ على كل الميزات ، وبصراحة ، لا يوجد أي معنى لإعادة تنفيذها في مصطلحات "rhl" . لذا - دعها تتقاعد.

سأحتاج إلى مزيد من التفاصيل حول ما جربته بالضبط وما تعنيه بكلمة "يعمل" و "لا يعمل".

هناك العديد من الأشياء التي يمكن أن تسوء إذا لم يتم تنفيذ تكامل مجمع الوحدات بشكل صحيح (وهو الموضوع أو هذا الموضوع). أتوقع أنه لا يوجد شيء في React نفسها يمنع الاسترداد من الأخطاء التي تحدث أثناء عمليات التحرير. يمكنك التحقق من أنه يعمل في React Native 0.61 RC3.

بعد بعض التعديلات ، يمكنني التحقق من أنها تعمل.

ومع ذلك - يبدو أن المكون الإضافي babel لم يكن يعمل مع الفصول الدراسية. لقد تحققت ويبدو أن هذا يحدث مع مراعاة أقل من التنفيذ ، حيث تعمل جميع الكودات المحقونة و react-refresh/runtime بشكل صحيح. لست متأكدًا مما إذا كان هذا مقصودًا أم أنه خاص بـ webpack ، إذا كان هذا الأخير يمكنني محاولة الحصول على إصلاح غدًا. (لقد اختبرت هذا أيضًا باستخدام الضبط المسبق للمترو فقط ، أعد إنتاج الفكرة هنا )

أنا متأكد من أنه يعمل مع RN ، لكن على جهازي الحالي ليس لدي بيئة سهلة للاختبار على RN ، لذا إذا كان بإمكانك توجيهي إلى تنفيذ المكون الإضافي babel في المترو أو التحويلات التي ستكون مفيدة حقًا.

نظرًا لوجود عدد قليل من النماذج الأولية ، فهل يجب أن نختار واحدة ثم ننقل هذه المناقشة إلى قضاياها؟ وتكرر هناك.

ربما يمكننا الذهاب هنا ؟ منذ تعليقي الأخير ، قمت بنقل المشروع بأكمله إلى JS العادي وأضفت بعض الإصلاحات في قائمة انتظار التحديث. لم أضطر إلى نقل المكون الإضافي لـ byapostolos ومنطق HMR الجديد في webpack @ بعد ذلك ، يجب أن تكون الإصلاحات مباشرة.

نعم ، لن يقوم المكون الإضافي Babel بتسجيل الفصول الدراسية. القصد من ذلك هو أن يحدث هذا على مستوى نظام الوحدة. يجب فحص كل عملية تصدير للتأكد من أنها مكون React "محتمل". (يتم توفير وظيفة التحقق من خلال وقت التشغيل.) إذا كان هذا صحيحًا ، فقم بتسجيل التصدير ، تمامًا مثل ملحق Babel الإضافي. أعطه معرفًا مثل filename exports%export_name . هذا ما يجعل الفصول الدراسية تعمل في Metro ، حيث لن يجدها المكون الإضافي Babel.

بعبارة أخرى ، نظرًا لأنه لا يمكننا الحفاظ على حالة الفصل على أي حال ، فقد نعهد أيضًا "بتحديد موقعهم" إلى حدود صادرات الوحدة بدلاً من محاولة العثور عليها مضمنة في الكود المصدري مع التحويل. يجب أن تكون عمليات التصدير بمثابة "التقاط الكل" للمكونات التي لم نجدها مع المكون الإضافي.

بدأت Mailchimp في استخدام تفرع من المكون الإضافي الذي قمت بمشاركته مؤخرًا. لقد تم تجسيده أكثر قليلاً ويبدو أن الأشخاص الذين اختاروا استخدامه سعداء جدًا. سنواصل تكرارها محليًا. أخطط لإزالة مفترق الطرق ونشر التحديثات في المنبع بمجرد مرورها قليلاً.

خواطر gaearon حول إضافة رمز يمكننا إرفاقه بالأشياء التي نعلم أنها آمنة للتحديث ، ولكن ليست مكونات؟ على سبيل المثال لدينا نمط مثل:

export default create({
  id: '100'
})

export const View = () => <div />

حيث يقوم create بإرجاع كائن. لقد قمت بتصحيحه الآن من ناحيتي ، ولكن يمكننا بسهولة إضافة رمز إلى كائن التصدير الافتراضي هناك يشير إلى أن هذا ملف آمن. لست متأكدا من أفضل نمط بالضبط.

تحرير: لقد أدركت أن هذا يمكن أن يدخل في تنفيذ التحديث! اعتقدت أنه قد يكون أفضل في وقت التشغيل ولكن ربما لا. مع وجود العديد من الأدوات المختلفة للودر ، قد يكون من الأفضل أن يكون لديك طريقة قياسية.

لنقدم 10 سنوات. كيف تبدو قاعدة بياناتك؟ السماح هنا ، عدم السماح هناك؟ كيف تحافظ على تحديث هذه الأعلام؟ كيف تفكر؟ مثل وجود مواقع آمنة للتحديث و _ غير آمنة_ ، عليك الحفاظ عليها أو لا يمكنك التوفيق بينها بشكل صحيح لسبب ما. ما الأسباب في كل حالة هي أسباب وجيهة؟

  • الذي symbols سيكون لديك المزيد - إعادة تحميل حوالي force allow أو إعادة تحميل force disallow
  • لماذا قد ترغب في خفض حد انتشار التحديث (أي قبول التحديث على حد "هذه" الوحدة النمطية) ، أو ترغب في رفعه (على سبيل المثال ، قبول التحديث على "حدود" الوحدة النمطية)
  • ماذا سيحدث إذا لم يتم وضع حدود؟ هل هي مشكلة في الأداء فقط ، أم أنه قد يحدث شيء أكثر خطورة؟

مرحبًا يا رفاق ، أتطلع إلى تقديم يد المساعدة هنا. هل اتفقنا على إعادة شراء / جهد واحد؟

هل هذا الريبو مشترك بواسطةpmmmwh؟
https://github.com/pmmmwh/react-refresh-webpack-plugin

أم هل هذا الريبو مشترك بواسطةmaisano؟
https://github.com/maisano/react-refresh-plugin

يبدو أنه تم الالتزام به مؤخرًا بواسطة pmmmwh . ما لم أسمع خلاف ذلك ، سأفترض أن هذا هو الشيء الذي يجب التركيز عليه.

بدأ التنفيذ في القسيمة 2 هنا: https://github.com/parcel-bundler/parcel/pull/3654

الصيف!

لأي شخص يبحث عنه ، تنفيذ React Refresh لمشاريع Rollup باستخدام Nollup للتطوير: https://github.com/PepsRyuu/rollup-plugin-react-refresh

ربما ليس التطبيق الأنظف ، لكنه يعمل.

بالنسبة لحلول حزمة الويب ، يبدو أنه لم يكن هناك أي إصدار رسمي للمكونات الإضافية المذكورة أعلاه ، لذلك يبدو أن أفضل حل HMR للتفاعل هو مكتبة دان هنا: https://github.com/gaearon/react-hot-loader

لقد قمنا للتو بشحن Parcel 2 alpha 3 مع دعم التحديث السريع خارج الصندوق! لا تتردد في تجربته. 😍 https://twitter.com/devongovett/status/1197187388985860096؟s=20

🥳 إضافة ملاحظة الإهمال إلى RHL

وصفة كنت أستخدمها لتجربة ذلك على تطبيقات CRA باستخدام عمل قيد التقدم و react-app-rewired و customize-cra :

npx create-react-app <project_dir> --typescript

npm install -D react-app-rewired customize-cra react-refresh babel-loader https://github.com/pmmmwh/react-refresh-webpack-plugin

تحرير ./package.json :

  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
  },

أضف ملف ./config-overrides.js :

// eslint-disable-next-line
const { addBabelPlugin, addWebpackPlugin, override } = require('customize-cra');
// eslint-disable-next-line
const ReactRefreshPlugin = require('react-refresh-webpack-plugin');

/* config-overrides.js */
module.exports = override(
  process.env.NODE_ENV === 'development'
    ? addBabelPlugin('react-refresh/babel')
    : undefined,
  process.env.NODE_ENV === 'development'
    ? addWebpackPlugin(new ReactRefreshPlugin())
    : undefined,
);

الاستمتاع بالتجربة حتى الآن. شكرا لكل العمل من جميع المعنيين!

شكرا @ drather19 !

لقد أنشأت مستودعًا بناءً على تعليماتك ، وهو يعمل:https://github.com/jihchi/react-app-rewired-react-refreshإذا رغب شخص ما في تجربته وحفظ بعض الكتابة ، فلا تتردد في استنساخ الريبو.


يرجى الرجوع إلى https://github.com/pmmmwh/react-refresh-webpack-plugin/tree/master/examples/cra-kitchen-sink

AND ... v0.1.0 لـ Webpack تم شحنه للتو 🎉

تضمين التغريدة
قد ترغبون يا رفاق في التبديل إلى هذا الإصدار - فهو يتضمن تجربة أكثر توحيدًا بالإضافة إلى الكثير من إصلاحات الأخطاء في التنفيذ الأولي.

pmmmwh يدعم ts-loader + babel-loader ؟

pmmmwh يدعم ts-loader + babel-loader ؟

لقد قمت باختبار TS مع Babel فقط وهو يعمل ، لذلك إذا لم ينجح عند استخدام لوادر ts + babel ، فلا تتردد في تقديم مشكلة :)

@ drather19 حاولت استنساخ الريبو وتشغيله لكن خادم dev لم يبدأ.

بيئة،
OS - OSX 10.14.6
العقدة - الإصدار 12.13.0
غزل -1.19.2

pmmmwh - لمعلوماتك

react-app-rewired-react-refresh on  master is 📦 v0.1.0 via ⬢ v12.13.0
❯ yarn start
yarn run v1.19.2
$ react-app-rewired start | cat
ℹ 「wds」: Project is running at http://192.168.1.178/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/seanmatheson/Development/temp/react-app-rewired-react-refresh/public
ℹ 「wds」: 404s will fallback to /index.html
Starting the development server...


@ drather19 حاولت استنساخ الريبو وتشغيله لكن خادم dev لم يبدأ.

بيئة،
OS - OSX 10.14.6
العقدة - الإصدار 12.13.0
غزل -1.19.2

pmmmwh - لمعلوماتك

react-app-rewired-react-refresh on  master is 📦 v0.1.0 via ⬢ v12.13.0
❯ yarn start
yarn run v1.19.2
$ react-app-rewired start | cat
ℹ 「wds」: Project is running at http://192.168.1.178/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/seanmatheson/Development/temp/react-app-rewired-react-refresh/public
ℹ 「wds」: 404s will fallback to /index.html
Starting the development server...

تم إصلاح ذلك في الفرع master للمكوّن الإضافي ، وسيتم إصداره غدًا.

تمكنت من الحصول علىpmmmwh الصورة webpack المساعد تعمل مع نسخة مطبوعة على الآلة الكاتبة رد فعل التطبيق باستخدام بابل. ومع ذلك ، تستغرق الإنشاءات الإضافية حوالي 12 ثانية بدلاً من حوالي ثانيتين باستخدام محمل ts فقط. سأستمر في اللعب بهذا لمعرفة ما إذا كنت أفتقد شيئًا ما على جانب تكوين babel مما يجعل الأداء أقرب ، لكنه في الوقت الحالي غسيل مقارنة بـ ts-loader والتحديثات الكاملة.

IronSean الرجاء الإبلاغ عنها في الريبو من هذا البرنامج المساعد؟ هذا لا يبدو طبيعيا.

سأستمر في اللعب بهذا لمعرفة ما إذا كنت أفتقد شيئًا ما على جانب تكوين babel مما يجعل الأداء أقرب ، لكنه في الوقت الحالي غسيل مقارنة بـ ts-loader والتحديثات الكاملة.

هل تمانع في نشر config / setup الخاص بك هناك؟ لن أكون قادرًا على اكتشاف المشكلات بدون مزيد من السياق.

pmmmwh لقد فتحت هذه المشكلة لنقل المناقشة إلى الريبو الخاص بك بمجرد أن تأكدت من أن المكون الإضافي الخاص بك هو الذي يحدث الفرق:
https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/20

هل سيعمل react-refresh ( React Fast Refresh ؟) مع Preact ، أم أن react-hot-loader هو الحل طويل الأمد لـ Preact؟

Jumblemuddle الذي يعتمد على Preact لكن يجب أن يكونوا قادرين على الاندماج مع Fast Refresh إذا أرادوا ذلك.

بالنسبة لأفراد CRA الراغبين في التشغيل باستخدام Fast Refresh ، فقد كان حظي أفضل مع craco (مقابل رد فعل التطبيق-إعادة توصيله + تخصيص- cra) الآن عبر craco.config.js :

// eslint-disable-next-line
const { whenDev } = require('@craco/craco');
// eslint-disable-next-line
const ReactRefreshPlugin = require('react-refresh-webpack-plugin');

module.exports = {
  webpack: {
    configure: webpackConfig => {
      if (process.env.NODE_ENV === 'development') {
        webpackConfig.module.rules.push({
          test: /BabelDetectComponent\.js/,
          use: [
            {
              loader: require.resolve('babel-loader'),
              options: {
                plugins: [require.resolve('react-refresh/babel')],
              },
            },
          ],
        });
        webpackConfig.module.rules.push({
          test: /\.[jt]sx?$/,
          exclude: /node_modules/,
          use: [
            {
              loader: require.resolve('babel-loader'),
              options: {
                presets: [
                  '@babel/react',
                  '@babel/typescript',
                  ['@babel/env', { modules: false }],
                ],
                plugins: [
                  '@babel/plugin-proposal-class-properties',
                  '@babel/plugin-proposal-optional-chaining',
                  '@babel/plugin-proposal-nullish-coalescing-operator',
                  'react-refresh/babel',
                ],
              },
            },
          ],
        });
      }
      return webpackConfig;
    },
    plugins: [
      ...whenDev(
        () => [new ReactRefreshPlugin({ disableRefreshCheck: false })],
        [],
      ),
    ],
  },
};

على وجه الخصوص ، ستتيح لك إضافة webpackConfig.optimization.runtimeChunk = false; إضافة / إزالة الخطافات والتحديث السريع بأمان.

الاستمتاع بالتجربة المحسنة أكثر الآن. بفضل @ mmhand123 للنصيحة عبر https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/25! (<- حل!)

بناءً على اقتراح @ drather19 ، قمت بنشر مكون إضافي esetnik / custom-cra-reaction-refresh .

بفضل @ drather19 ، قمت بتعديل الرمز قليلاً الآن ويمكنه العمل في إعداد monorepo مساحة عمل الغزل.

أولاً ، قم بتثبيت ما يلي في الحزم الفرعية التي تريد تمكين التحديث السريع لها:

"@craco/craco": "^5.6.3", "@pmmmwh/react-refresh-webpack-plugin": "^0.2.0", "webpack-hot-middleware": "^2.25.0"

ثم أضف هذا إلى craco.config.js :

;(function ForbidCRAClearConsole() {
    try {
        require('react-dev-utils/clearConsole')
        require.cache[require.resolve('react-dev-utils/clearConsole')].exports = () => {}
    } catch (e) {}
})()

const { whenDev } = require('@craco/craco')
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin')

module.exports = {
    webpack: {
        configure: webpackConfig => {
            whenDev(() => {
                // Work around monorepo setup when using yarn workspace hoisted packages
                // without the need to list 'babel-loader' and 'babel-preset-react-app' as
                // dependencies to avoid duplication since 'react-scripts' already has them.
                const reactLoaderConfig = webpackConfig.module.rules
                    .find(x => Array.isArray(x.oneOf))
                    .oneOf.find(
                        x =>
                            x.options &&
                            x.options.presets &&
                            x.options.presets.some(p => p.includes('babel-preset-react-app')) &&
                            x.loader &&
                            typeof x.loader.includes === 'function' &&
                            x.loader.includes('babel-loader') &&
                            x.test &&
                            typeof x.test.test === 'function' &&
                            x.test.test('x.tsx') &&
                            x.test.test('x.jsx'),
                    )

                if (reactLoaderConfig) {
                    webpackConfig.module.rules.push({
                        test: /BabelDetectComponent\.js/,
                        use: [
                            {
                                loader: reactLoaderConfig.loader,
                                options: {
                                    plugins: [require.resolve('react-refresh/babel')],
                                },
                            },
                        ],
                    })

                    webpackConfig.module.rules.push({
                        test: /\.[jt]sx?$/,
                        exclude: /node_modules/,
                        use: [
                            {
                                loader: reactLoaderConfig.loader,
                                options: {
                                    presets: reactLoaderConfig.options.presets,
                                    plugins: [require.resolve('react-refresh/babel')],
                                },
                            },
                        ],
                    })
                } else {
                    console.error('cannot find react app loader')
                }

                // console.debug(require('util').inspect(webpackConfig.module.rules, { colors: true, depth: null }))
            })

            return webpackConfig
        },
        plugins: [whenDev(() => new ReactRefreshPlugin({ disableRefreshCheck: false }))].filter(Boolean),
    },
}

gaearon هل نتوقع أن يصبح التحديث السريع متاحًا في CRA افتراضيًا في وقت ما؟
إذا كان الأمر كذلك فما هو المطلوب لذلك؟

مطلوب قدر من العمل لذلك :-) الذي يتم إجراؤه حاليًا.

إذا كان استخدام وظائف HMR سوف يسمى؟ على سبيل المثال componentDidMount.
أنا أستخدم الوكيل التفاعلي وسيتم استدعاء componentDidMount.
ويتفاعل 15.X هل يمكن استخدام التحديث السريع؟

  • سيتم استدعاء componentDidMount . بالإضافة إلى unmount - سيتم إعادة تحميل الفصول بالكامل.
  • وهو الوقت المناسب للتوقف عن استخدام react-proxy . حسنًا ، ربما يجب أن تتوقف منذ بضع سنوات.
  • 15.X ؟ - بالطبع لا. التحديث السريع __ هو جزء__ من التفاعل ، وبالتالي فهو موجود فقط في الإصدارات الحديثة.

لذلك يجب علينا استخدام التحديث السريع أو رد الفعل - المحمل الساخن لاستبدال وكيل رد الفعل؟
هل هناك طريقة لمنع الدوال (componentDidMount) من التنفيذ لـ HMR؟ - سيستدعي طريقة للحصول على بيانات جديدة.

كيف يمكنني استخدام محمل رد الفعل الساخن في JIT؟ - تجميع المتصفح في الوقت الحقيقي

  • لذلك يجب علينا استخدام التحديث السريع أو رد الفعل - المحمل الساخن لاستبدال وكيل رد الفعل؟

    جرب fast refresh أولاً ، ثم RHL

  • هل هناك طريقة لمنع الدوال (componentDidMount) من التنفيذ لـ HMR؟ - سيستدعي طريقة للحصول على بيانات جديدة.

    (استخدم الخطافات ...) ، لا تعتمد على دورة حياة المكون ، أحضر البيانات عند الحاجة. جرب react-query ، swr أو حلول أخرى.

بالنسبة للسؤال حول كيفية استخدام HMR الجديد ، لا أعتقد أنني أعرف أحدث الأفكار هناك. أرى أن gaearon لديه علاقات عامة خاصة بالمسح في مستودع CRA

للتوضيح للقراء ، أن العلاقات العامة قديمة جدًا ولم تعد ذات صلة.

أحتاج إلى كتابة شيء ما حول كيفية عمل التحديث السريع وكيفية دمجه. لم يتح لها الوقت بعد.

حتى اليوم ، لا تزال هذه العلاقات العامة مفتوحة. سيكون من الجيد أن تظل العلاقات العامة ذات الصلة التي لا تزال لديها فرصة للدمج مفتوحة للحصول على نظرة عامة أفضل. إذا كنت تحتفظ بها فقط كمرجع ، فإنني أوصي بنقل الأشياء إلى فرع أو علامة أو مستودع مختلف.

أستمر في الحصول على Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly. لقد اتبعت الوثائق ولكن يبدو أنني قد فاتني شيء ما؟

ما زلت أتلقى خطأ: لم يتم تمكين [رد الفعل تحديث] استبدال الوحدة النمطية الساخنة (HMR)! يتطلب React Refresh أن يعمل HMR بشكل صحيح. لقد اتبعت الوثائق ولكن يبدو أنني قد فاتني شيء ما؟

silkfire أفترض أنك تستخدم المكون الإضافي webpack. إذا كانت الإجابة بنعم ، يرجى تقديم سؤالك في repo plugin webpack: https://github.com/pmmmwh/react-refresh-webpack-plugin/.

حتى اليوم ، لا تزال هذه العلاقات العامة مفتوحة. سيكون من الجيد أن تظل العلاقات العامة ذات الصلة التي لا تزال لديها فرصة للدمج مفتوحة للحصول على نظرة عامة أفضل. إذا كنت تحتفظ بها فقط كمرجع ، فإنني أوصي بنقل الأشياء إلى فرع أو علامة أو مستودع مختلف.

أنا أقدر اقتراحك ، ولكن مع وجود الآلاف من الإخطارات غير المقروءة ، قد يكون من الصعب بالنسبة لي أحيانًا تذكر إعادة زيارة العلاقات العامة القديمة. أثق في أن المشرفين على مستودعات Create React App يفعلون الشيء الصحيح ويغلقونه إذا رأوا أنه غير مفيد بعد الآن.

سأغلق هذا.

لدينا https://github.com/pmmmwh/react-refresh-webpack-plugin/ كتطبيق مرجعي لـ webpack.
و https://github.com/facebook/react/issues/16604#issuecomment -528663101 يشرح كيفية إجراء تكامل مخصص.

أستمر في الحصول على Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly. لقد اتبعت الوثائق ولكن يبدو أنني قد فاتني شيء ما؟

يبدو أنك لم تقم بتمكين webpack HMR. لمزيد من المساعدة ، يرجى تقديم مشكلة في الريبو الخاص بالملحق.

نظرًا لأن Hot Replacement أصبحت الآن جزءًا من React - يجب أن يكون لها مكان منفصل في توثيق React ، مشيرًا إلى المكتبات الإضافية التي سيتم استخدامها مع حزم ومنصات معينة ، بالإضافة إلى شرح بعض عمليات الاستبدال التي لا تزال موجودة ، مثل ملفات css ذاتية التحديث الوحدات.

لا ينبغي دفن معلومات مثل هذه في إصدارات github ومشاركات المدونات.

theKashey إنه في المثال .
أيضًا ، هناك تطبيق تحديث سريع سيتم دمجه مع تطبيق create-react-app ، لكن لم يتم إصداره بعد: pmmmwh / رد فعل-تحديث-webpack-plugin # 7. ربما سيكون في إصدار البرامج النصية التالية.

لذلك ربما لا يشعر فريق React حاليًا أنه من الصواب التحدث عن Fast Refresh من أجل رد الفعل في هذه المرحلة التجريبية حتى الآن.

إنه موجود في React ، لكن تنفيذ رد الفعل هو تجريبي واحد فقط.

لكي نكون واضحين ، فإن التنفيذ في react-dom نفسه مستقر ، تمامًا كما هو الحال في React Native. كل ما في الأمر أن عمليات الدمج ليست كلها مستقرة.

يجب أن يكون لها مكان منفصل في وثائق React ، مشيرًا إلى المكتبات الإضافية التي سيتم استخدامها مع حزم ومنصات معينة ، بالإضافة إلى شرح بعض حالات الاستحواذ الموجودة ، مثل وحدات css ذاتية التحديث.

هذا يبدو معقولا. يسعدني أن أحصل على علاقات عامة لإضافته إلى قسم الأدلة المتقدمة ، ربما بناءً على صفحة RN مماثلة .

تضمين التغريدة
تطبيق رد الفعل الخاص بي على ما يرام مع بعض تغييرات المكونات المصممة وتطبيق هذه التغييرات بشكل صحيح دون أي مشاكل.
ومع ذلك ، عندما أقوم بتغيير بعض التعليمات البرمجية في مخفض Redux ، يتم تحديث التطبيق بالكامل بشدة ويفقد جميع حالات الإعادة.
هل أحتاج إلى استخدام بعض المكتبات الأخرى مثل redux-persist لحفظ الحالة الحالية مع react-fast-refresh ؟

لقد ذهبنا إلى دائرة كاملة وها نحن ذا مرة أخرى 😅

هذه هي الطريقة التي تعمل بها HMR منخفضة المستوى ، وهي خارج نطاق مسؤولية التحديث السريع. يرجى الرجوع إلى مستندات redux أو webpack

لقد ذهبنا إلى دائرة كاملة وها نحن ذا مرة أخرى 😅

هذه هي الطريقة التي تعمل بها HMR منخفضة المستوى ، وهي خارج نطاق مسؤولية التحديث السريع. يرجى الرجوع إلى مستندات redux أو webpack

هل يمكنك ربط مرجع الدائرة الكاملة؟

@ jwchang0206 تأكد من أن لديك رمز مثل هذا في متجرك.

هل يمكنك ربط مرجع الدائرة الكاملة؟

تم طرح نفس الأسئلة على React Hot Loader. أعطيت نفس الإجابات. نحن في بداية دورة جديدة.

@ jwchang0206 انظر إلى حاقن الاختزال ، مكتبة صغيرة كتبتها لمعالجة هذه المشكلة.
سيسمح لك بدعم إعادة تحميل المخفضات بإعادة التحميل الساخن.
تأكد من اتباع مبادئ إعادة الثبات في مخفضاتك وستعمل بسلاسة 💯
وإذا كنت تستخدم sagas ، فيمكنك استخدام حاقن redux-sagas .

gaearon أنا مرتبك قليلاً باستخدام window . لا يبدو لي كما لو كان ضروريًا حقًا لأنه تم تبديل التنفيذ؟ ما الجدوى من ذلك؟

var prevRefreshReg = window.$RefreshReg$; // these are dummies
var prevRefreshSig = window.$RefreshSig$; // these are dummies
var RefreshRuntime = require('react-refresh/runtime');

window.$RefreshReg$ = (type, id) =>{ /*...*/ }
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;

try {
  // ...
} finally {
  window.$RefreshReg$ = prevRefreshReg; // these are dummies again
  window.$RefreshSig$ = prevRefreshSig; // these are dummies again
}

لديّ أداة تجميع مخصصة خاصة بي وأنا بصدد تنفيذ هذا ولكن لا يمكنني معرفة سبب كون ذلك ضرورة مطلقة أو ما هو الهدف منه ... في البداية كنت أشك في بعض تحسين استخدام / تسرب الذاكرة ولكن هذه هي مجرد مكالمات تم تحويلها إلى RefreshRuntime ...

leidegre لا يمكنني التعليق على قرار تعيين $ RefreshSig $ على كائن النافذة ، لكن الاقتران ببيئة المتصفح تسبب لي في مشاكل عند استهلاك Fast Refresh في React NativeScript. جاء pmmmwh للإنقاذ من خلال تكييف المكون الإضافي Fast Refresh Webpack الخاص به للتغلب على اقتران Fast Refresh بالمتصفح (تمت مناقشة المشكلات التي تمت مواجهتها والتغلب عليها في هذا الموضوع: https://github.com/pmmmwh/react-refresh-webpack-plugin/ القضايا / 79). أتساءل عما إذا كان الأسلوب المستخدم سيكون مفيدًا لك في تكامل أداة التجميع المخصصة الخاصة بك للتحديث السريع.

تعد أداة التجميع الخاصة بي في الغالب عبارة عن غلاف حول مترجم TypeScript. التنفيذ هو في الغالب هذا ، مقتبس من الزائر react-refresh/babel .

هذا مجرد شيء بسيط يعمل ولكنه ليس كاملاً مثل الزائر react-refresh/bable .

import ts = require("typescript")

import { IndexModule } from "./registry"

/** Enables the use of `react-refresh` for hot reloading of React components. */
export function hotTransform(m: IndexModule, hot: boolean) {
  // see https://github.com/facebook/react/issues/16604#issuecomment-528663101
  return (ctx: ts.TransformationContext) => {
    return (sourceFile: ts.SourceFile) => {
      const refreshRuntime = ts.createUniqueName("ReactRefreshRuntime")

      const createSignatureFunctionForTransform = ts.createPropertyAccess(
        refreshRuntime,
        "createSignatureFunctionForTransform"
      )

      const register = ts.createPropertyAccess(refreshRuntime, "register")

      let hasComponents = false

      function visitor(node: ts.Node): ts.VisitResult<ts.Node> {
        if (ts.isFunctionDeclaration(node)) {
          if (_hasModifier(node, ts.SyntaxKind.ExportKeyword)) {
            // assert component naming convention

            if (node.name === undefined) {
              console.warn("unsupported export of unnamed function in ...")
              return node
            }

            const name = node.name
            if (!_isComponentName(name.text)) {
              console.warn(
                `warning: unsupported export '${name.text}' in ${m.path} (${m.id}) does not look like a function component, component names start with a capital letter A-Z. TSX/JSX files should only export React components.`
              )
              return node
            }

            if (!hot) {
              return node // opt-out
            }

            hasComponents = true

            let hookSignatureString = ""

            function hookSignatureStringVisitor(
              node: ts.Node
            ): ts.VisitResult<ts.Node> {
              const hookSig = _getHookSignature(node)
              if (hookSig !== undefined) {
                if (0 < hookSignatureString.length) {
                  hookSignatureString += "\n"
                }
                hookSignatureString += hookSig
              }
              return node
            }

            // update function body to include the call to create signature on render

            const signature = ts.createUniqueName("s")

            node = ts.visitEachChild(
              node,
              (node) => {
                if (ts.isBlock(node)) {
                  return ts.updateBlock(
                    ts.visitEachChild(node, hookSignatureStringVisitor, ctx),
                    [
                      ts.createExpressionStatement(
                        ts.createCall(signature, undefined, [])
                      ),
                      ...node.statements,
                    ]
                  )
                }
                return node
              },
              ctx
            )

            const signatureScope = ts.createVariableStatement(
              undefined,
              ts.createVariableDeclarationList(
                [
                  ts.createVariableDeclaration(
                    signature,
                    undefined,
                    ts.createCall(
                      createSignatureFunctionForTransform,
                      undefined,
                      undefined
                    )
                  ),
                ],
                ts.NodeFlags.Const
              )
            )

            const createSignature = ts.createExpressionStatement(
              ts.createCall(signature, undefined, [
                name,
                ts.createStringLiteral(hookSignatureString),
              ])
            )

            const registerComponent = ts.createExpressionStatement(
              ts.createCall(register, undefined, [
                name,
                ts.createStringLiteral(m.path + " " + name.text),
              ])
            )

            return [signatureScope, node, createSignature, registerComponent]
          }
        }

        if (!hot) {
          // if hot reloading isn't enable, remove hot reloading API calls
          if (ts.isExpressionStatement(node)) {
            const call = node.expression
            if (ts.isCallExpression(call)) {
              if (
                _isPropertyAccessPath(
                  call.expression,
                  "module",
                  "hot",
                  "reload"
                )
              ) {
                return undefined
              }
            }
          }
        }

        return node
      }

      sourceFile = ts.visitEachChild(sourceFile, visitor, ctx)

      if (hot && hasComponents) {
        let reactIndex = sourceFile.statements.findIndex((stmt) => {
          if (ts.isImportEqualsDeclaration(stmt)) {
            const ref = stmt.moduleReference
            if (ts.isExternalModuleReference(ref)) {
              const lit = ref.expression
              if (ts.isStringLiteral(lit)) {
                return lit.text === "react"
              }
            }
          }
          return false
        })

        if (reactIndex === -1) {
          console.warn(`cannot find import React = require('react') in ...`)
          reactIndex = 0
        }

        // insert after

        sourceFile = ts.updateSourceFileNode(sourceFile, [
          ...sourceFile.statements.slice(0, reactIndex + 1),
          ts.createImportEqualsDeclaration(
            undefined,
            undefined,
            refreshRuntime,
            ts.createExternalModuleReference(
              ts.createStringLiteral("react-refresh/runtime")
            )
          ),
          ...sourceFile.statements.slice(reactIndex + 1),
          ts.createExpressionStatement(
            ts.createCall(
              ts.createPropertyAccess(
                ts.createPropertyAccess(
                  ts.createIdentifier("module"),
                  ts.createIdentifier("hot")
                ),
                ts.createIdentifier("reload")
              ),
              undefined,
              undefined
            )
          ),
          ts.createExpressionStatement(
            ts.createBinary(
              ts.createPropertyAccess(
                ts.createIdentifier("globalThis"),
                ts.createIdentifier("__hot_enqueueUpdate")
              ),
              ts.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
              ts.createCall(
                ts.createPropertyAccess(
                  ts.createIdentifier("globalThis"),
                  ts.createIdentifier("__hot_enqueueUpdate")
                ),
                undefined,
                undefined
              )
            )
          ),
        ])
      }

      return sourceFile
    }
  }
}

function _hasModifier(node: ts.Node, kind: ts.SyntaxKind): boolean {
  const modifiers = node.modifiers
  if (modifiers !== undefined) {
    for (let i = 0; i < modifiers.length; i++) {
      if (modifiers[i].kind === kind) {
        return true
      }
    }
  }
  return false
}

function _isComponentName(name: string): boolean {
  // ^[A-Z]
  const ch0 = name.charCodeAt(0)
  return 0x41 <= ch0 && ch0 <= 0x5a
}

function _isPropertyAccessPath(
  node: ts.Expression,
  ...path: ReadonlyArray<string>
): node is ts.PropertyAccessExpression {
  for (let i = 0; i < path.length; i++) {
    if (ts.isPropertyAccessExpression(node)) {
      if (!(node.name.text === path[path.length - (i + 1)])) {
        return false
      }
      node = node.expression
    }
  }
  return true
}

function _getHookSignature(node: ts.Node): string | undefined {
  if (ts.isExpressionStatement(node)) {
    const call = node.expression
    if (ts.isCallExpression(call)) {
      const prop = call.expression
      if (ts.isPropertyAccessExpression(prop)) {
        const text = prop.name.text
        if (text.startsWith("use") && 3 < text.length) {
          // todo: add additional checks and emit warnings if the hook usage looks non standard

          return text
        }
      }
    }
  }
  return undefined
}

لم أكن متأكدًا من كيفية استخدام createSignatureFunctionForTransform في البداية ، لكنها مجرد وظيفة مصنع تخلق آلة حالة صغيرة لكل مكون. لذلك قمت باستدعائها مرة واحدة لكل وظيفة باستخدام توقيع الخطاف الثابت (وهو مجرد قيمة غير شفافة ، مثل التجزئة). ثم تقوم باستدعائها من التقديم حتى تنتهي من عمل الإعداد.

يغير شيئًا مثل هذا:

import React = require("react")

export function App() {
  const [state, setState] = React.useState(0)

  return (
    <React.Fragment>
      <p>
        Click Count !!!<strong>{state}</strong>!!!
        <br />
        <button onClick={() => setState((acc) => acc + 1)}>Click me</button>
      </p>
    </React.Fragment>
  )
}

في هذا:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const React = require("react");
const ReactRefreshRuntime_1 = require(6);
const s_1 = ReactRefreshRuntime_1.createSignatureFunctionForTransform();
function App() {
    s_1();
    const [state, setState] = React.useState(0);
    return (React.createElement(React.Fragment, null,
        React.createElement("p", null,
            "Click Count !!!",
            React.createElement("strong", null, state),
            "!!!",
            React.createElement("br", null),
            React.createElement("button", { onClick: () => setState((acc) => acc + 1) }, "Click me"))));
}
exports.App = App;
s_1(App, "useState");
ReactRefreshRuntime_1.register(App, "./App App");
module.hot.reload();
globalThis.__hot_enqueueUpdate && globalThis.__hot_enqueueUpdate();

لاحظ أن الزائر غير مكتمل. إنه يتعامل فقط مع حالات الاستخدام الأساسية.

أنا مرتبك قليلاً باستخدام window . لا يبدو لي كما لو كان ضروريًا حقًا لأنه تم تبديل التنفيذ؟ ما الجدوى من ذلك؟

تضمين التغريدة

أعتقد أن التطبيق في Metro لا يستخدم window بل النطاق الفعلي global .

لا أعلم عن الأساس المنطقي الأصلي لهذا التنفيذ ، ولكنه كان مفيدًا من تجربتي - فهو يضمن أن منطق الطلب الفعلي مستقل عن منطق التحديث السريع (مما يعني أنه يمكن استخدام التحويلات react-refresh/babel مع أي تحويلات تقريبًا التجميع). كما هو الحال مع المقايضة ، فإنه يعمل أيضًا كحارس لضمان عدم معالجة الوحدات التي لا يُفترض أن تتم معالجتها بحلول وقت التشغيل:

ضع في اعتبارك حالة يتم فيها استخدام @babel/runtime ، والتي ستضخ المساعدين كواردات إلى الحزمة وتريد فقط رمز HMR غير node_modules . إذا لم تقم أولاً بتهيئة المساعدين الفارغين ومع ذلك تقوم بتعيين مساعدين للنطاق العالمي ، فقد تحدث حالة نادرة حيث سيتصل المساعدون المحقون من قبل Babel cleanup قبل أن تنتهي وحدة أرض المستخدم من التهيئة (لأنهم أطفال الواردات).

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات