React: Wie sollten wir Apps für HMR einrichten, nachdem Fast Refresh den React-Hot-Loader ersetzt?

Erstellt am 29. Aug. 2019  ·  85Kommentare  ·  Quelle: facebook/react

Dan Abramov erwähnte, dass Devtools v4 react-hot-loader obsolet machen wird: https://twitter.com/dan_abramov/status/1144715740983046144?s=20

Mich:
Ich habe diesen Haken:
require("react-reconciler")(hostConfig).injectIntoDevTools(opts);
Aber HMR hat immer ganz ohne gearbeitet. Ist das jetzt eine neue Anforderung?

Dan:
Ja, das ist es, was der neue Mechanismus verwendet. Der neue Mechanismus benötigt keinen "React-Hot-Loader". Wenn Sie also ein Update durchführen, möchten Sie dieses Paket entfernen. (Es ist ziemlich invasiv)

Ich kann jedoch keine Erwähnung von HMR in der Devtools-Dokumentation sehen. Nun, da react-hot-loader veraltet ist (und damit die require("react-hot-loader/root").hot -Methode), wie sollen wir Apps für HMR einrichten in:

  • Reagiere auf DOM-Apps
  • Reagiere auf native Apps
  • Reagieren Sie auf benutzerdefinierte Renderer-Apps

Ich würde mich besonders für einen Migrationsleitfaden speziell für alle interessieren, die HMR bereits über react-hot-loader .

Ist es für HMR auch wichtig, ob wir die eigenständigen Devtools oder die Browsererweiterung Devtools verwenden?

Question

Hilfreichster Kommentar

Okay, hier geht.

Was ist eine schnelle Aktualisierung?

Es ist eine Neuimplementierung von "Hot Reloading" mit voller Unterstützung von React. Es wird ursprünglich für React Native ausgeliefert, aber der größte Teil der Implementierung ist plattformunabhängig. Es ist geplant, es allgemein zu verwenden - als Ersatz für reine Userland-Lösungen (wie react-hot-loader ).

Kann ich die Schnellaktualisierung im Web verwenden?

Theoretisch ja, das ist der Plan. Praktisch muss jemand es in im Web übliche Bundler (z. B. Webpack, Parcel) integrieren. Ich bin noch nicht dazu gekommen. Vielleicht möchte jemand es abholen. Dieser Kommentar ist eine grobe Anleitung, wie Sie es tun würden.

Woraus besteht es?

Bei der schnellen Aktualisierung arbeiten mehrere Teile zusammen:

  • "Hot Module Replacement" -Mechanismus im Modulsystem.

    • Dies wird in der Regel auch vom Bündler bereitgestellt.

    • Beispiel: Im Webpack können Sie dies module.hot API

  • React Renderer 16.9.0+ (zB React DOM 16.9)
  • react-refresh/runtime Einstiegspunkt
  • react-refresh/babel Babel Plugin

Sie werden wahrscheinlich am Integrationsteil arbeiten wollen. Dh Integration von react-refresh/runtime in den Webpack-Mechanismus zum Ersetzen heißer Module.

Wie sieht Integration aus?

⚠️⚠️⚠️ Um klar zu sein, ist dies ein Leitfaden für Menschen, die die Integration selbst umsetzen möchten. VERFAHREN SIE AUF EIGENE VORSICHT!

Es gibt einige Dinge, die Sie minimal tun möchten:

  • Aktivieren Sie HMR in Ihrem Bundler (z. B. Webpack).
  • Stellen Sie sicher, dass React 16.9.0+ ist
  • Fügen Sie react-refresh/babel zu Ihren Babel-Plugins hinzu

Zu diesem Zeitpunkt sollte Ihre App abstürzen. Es sollte Aufrufe von $RefreshReg$ und $RefreshSig$ Funktionen enthalten, die nicht definiert sind.

Dann müssen Sie einen neuen JS-Einstiegspunkt erstellen, der vor jedem Code in Ihrer App ausgeführt werden muss , einschließlich react-dom (!). Dies ist wichtig. Wenn es nach react-dom läuft, funktioniert nichts. Dieser Einstiegspunkt sollte ungefähr so ​​aussehen:

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

Dies sollte die Abstürze beheben. Aber es wird immer noch nichts tun, weil diese $RefreshReg$ und $RefreshSig$ Implementierungen Noops sind. Das Anschließen ist das Kernstück der Integrationsarbeit, die Sie erledigen müssen.

Wie Sie das machen, hängt von Ihrem Bundler ab. Ich nehme an, mit Webpack könnten Sie einen Loader schreiben, der vor und nach der Ausführung jedes Moduls Code hinzufügt. Oder vielleicht gibt es einen Haken, um etwas in die Modulvorlage einzufügen. Unabhängig davon möchten Sie erreichen, dass jedes Modul folgendermaßen aussieht:

// 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;
}

Die Idee hier ist, dass unser Babel-Plugin Aufrufe an diese Funktionen ausgibt, und dann bindet unsere obige Integration diese Aufrufe an die Modul-ID. Damit die Laufzeit Zeichenfolgen wie "path/to/Button.js Button" empfängt, wenn eine Komponente registriert wird. (Oder im Fall von Webpack wären IDs Zahlen.) Vergessen Sie nicht, dass sowohl die Babel-Transformation als auch diese Umhüllung nur im Entwicklungsmodus erfolgen dürfen.

Als Alternative zum Umschließen des Modulcodes gibt es möglicherweise eine Möglichkeit, einen Versuch / Endlich wie diesen an der Stelle hinzuzufügen, an der der Bundler die Modulfabrik tatsächlich initialisiert. Wie hier in Metro (RN Bundler). Dies wäre wahrscheinlich besser, da wir nicht jedes Modul aufblähen oder uns Gedanken über die Einführung einer illegalen Syntax machen müssten, z. B. wenn wir import in try / finally .

Sobald Sie dies angeschlossen haben, haben Sie ein letztes Problem. Ihr Bundler weiß nicht, dass Sie die Updates bearbeiten, daher wird die Seite wahrscheinlich trotzdem neu geladen. Sie müssen es nicht sagen. Dies ist wiederum bündlerspezifisch, aber der von mir vorgeschlagene Ansatz besteht darin, zu überprüfen, ob alle Exporte React-Komponenten sind , und in diesem Fall das Update zu "akzeptieren". Im Webpack könnte es so aussehen:


// ...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();
}

Was ist isReactRefreshBoundary ? Es ist eine Sache, die Exporte flach auflistet und bestimmt, ob nur React-Komponenten exportiert werden. So entscheiden Sie, ob Sie ein Update akzeptieren oder nicht. Ich habe es hier nicht kopiert und eingefügt, aber diese Implementierung könnte ein guter Anfang sein. (In diesem Code bezieht sich Refresh auf react-refresh/runtime Export).

Sie möchten auch alle Exporte manuell registrieren, da die Babel-Transformation nur $RefreshReg$ für Funktionen aufruft. Wenn Sie dies nicht tun, werden Aktualisierungen von Klassen nicht erkannt.

Schließlich wäre die enqueueUpdate() -Funktion etwas, das von Modulen gemeinsam genutzt wird, die das eigentliche React-Update entprellen und ausführen.

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

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

Zu diesem Zeitpunkt sollte etwas funktionieren.

Nuancen

Es gibt einige grundlegende Erwartungen an die Erfahrung, die mir wichtig sind und die in das Branding "Fast Refresh" einfließen. Es sollte in der Lage sein, einen Syntaxfehler, einen Modulinitialisierungsfehler oder einen Renderfehler ordnungsgemäß zu beheben. Ich werde nicht im Detail auf diese Mechanismen eingehen, aber ich bin der festen Überzeugung, dass Sie Ihr Experiment nicht "Fast Refresh" nennen sollten, bis es diese Fälle gut behandelt.

Leider weiß ich nicht, ob Webpack all diese unterstützen kann, aber wir können um Hilfe bitten, wenn Sie in einen etwas funktionierenden Zustand geraten, aber dann stecken bleiben. Ich habe zum Beispiel festgestellt, dass die accept() API des Webpacks die Fehlerbehebung erschwert (Sie müssen nach einem Fehler eine vorherige Version des Moduls akzeptieren), aber es gibt eine Möglichkeit, dies zu umgehen. Eine andere Sache, auf die wir zurückkommen müssen, ist die automatische "Registrierung" aller Exporte und nicht nur der Exporte, die vom Babel-Plugin gefunden wurden. Lassen Sie uns dies zunächst ignorieren, aber wenn Sie etwas haben, das beispielsweise für ein Webpack funktioniert, kann ich mir das Polieren ansehen.

In ähnlicher Weise müssten wir es in eine "Fehlerbox" -Erfahrung integrieren, ähnlich wie react-error-overlay in Create React App. Das hat einige Nuancen, wie das Fehlerfeld verschwinden sollte, wenn Sie einen Fehler beheben. Das erfordert auch einige weitere Arbeiten, die wir durchführen können, sobald die Stiftung eingerichtet ist.

Lassen Sie mich wissen, wenn Sie Fragen haben!

Alle 85 Kommentare

Es gibt einige Verwirrung. Die neuen DevTools ermöglichen kein Hot-Reloading (oder haben nichts mit dem Reloading zu tun). Vielmehr nutzen die Änderungen, an denen Dan gearbeitet hat, den "Hook", mit dem DevTools und React kommunizieren. Es fügt sich in die Mitte ein, damit es neu geladen werden kann.

Ich habe den Titel bearbeitet, um die Erwähnung von DevTools zu entfernen (da dies zu Verwirrung führen kann).

Was die Frage betrifft, wie das neue HMR verwendet werden soll, glaube ich nicht, dass ich die neuesten Überlegungen dort kenne. Ich sehe, dass @gaearon eine Wip-PR auf dem CRA-Repo hat:
https://github.com/facebook/create-react-app/pull/5958

Was die Frage betrifft, wie das neue HMR verwendet werden soll, glaube ich nicht, dass ich die neuesten Überlegungen dort kenne. Ich sehe, dass @gaearon eine Wip-PR auf dem CRA-Repo hat:

Um den Lesern zu verdeutlichen, dass PR sehr veraltet und nicht mehr relevant ist.


Ich muss etwas darüber aufschreiben, wie Fast Refresh funktioniert und wie es integriert wird. Ich hatte noch keine Zeit.

Okay, hier geht.

Was ist eine schnelle Aktualisierung?

Es ist eine Neuimplementierung von "Hot Reloading" mit voller Unterstützung von React. Es wird ursprünglich für React Native ausgeliefert, aber der größte Teil der Implementierung ist plattformunabhängig. Es ist geplant, es allgemein zu verwenden - als Ersatz für reine Userland-Lösungen (wie react-hot-loader ).

Kann ich die Schnellaktualisierung im Web verwenden?

Theoretisch ja, das ist der Plan. Praktisch muss jemand es in im Web übliche Bundler (z. B. Webpack, Parcel) integrieren. Ich bin noch nicht dazu gekommen. Vielleicht möchte jemand es abholen. Dieser Kommentar ist eine grobe Anleitung, wie Sie es tun würden.

Woraus besteht es?

Bei der schnellen Aktualisierung arbeiten mehrere Teile zusammen:

  • "Hot Module Replacement" -Mechanismus im Modulsystem.

    • Dies wird in der Regel auch vom Bündler bereitgestellt.

    • Beispiel: Im Webpack können Sie dies module.hot API

  • React Renderer 16.9.0+ (zB React DOM 16.9)
  • react-refresh/runtime Einstiegspunkt
  • react-refresh/babel Babel Plugin

Sie werden wahrscheinlich am Integrationsteil arbeiten wollen. Dh Integration von react-refresh/runtime in den Webpack-Mechanismus zum Ersetzen heißer Module.

Wie sieht Integration aus?

⚠️⚠️⚠️ Um klar zu sein, ist dies ein Leitfaden für Menschen, die die Integration selbst umsetzen möchten. VERFAHREN SIE AUF EIGENE VORSICHT!

Es gibt einige Dinge, die Sie minimal tun möchten:

  • Aktivieren Sie HMR in Ihrem Bundler (z. B. Webpack).
  • Stellen Sie sicher, dass React 16.9.0+ ist
  • Fügen Sie react-refresh/babel zu Ihren Babel-Plugins hinzu

Zu diesem Zeitpunkt sollte Ihre App abstürzen. Es sollte Aufrufe von $RefreshReg$ und $RefreshSig$ Funktionen enthalten, die nicht definiert sind.

Dann müssen Sie einen neuen JS-Einstiegspunkt erstellen, der vor jedem Code in Ihrer App ausgeführt werden muss , einschließlich react-dom (!). Dies ist wichtig. Wenn es nach react-dom läuft, funktioniert nichts. Dieser Einstiegspunkt sollte ungefähr so ​​aussehen:

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

Dies sollte die Abstürze beheben. Aber es wird immer noch nichts tun, weil diese $RefreshReg$ und $RefreshSig$ Implementierungen Noops sind. Das Anschließen ist das Kernstück der Integrationsarbeit, die Sie erledigen müssen.

Wie Sie das machen, hängt von Ihrem Bundler ab. Ich nehme an, mit Webpack könnten Sie einen Loader schreiben, der vor und nach der Ausführung jedes Moduls Code hinzufügt. Oder vielleicht gibt es einen Haken, um etwas in die Modulvorlage einzufügen. Unabhängig davon möchten Sie erreichen, dass jedes Modul folgendermaßen aussieht:

// 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;
}

Die Idee hier ist, dass unser Babel-Plugin Aufrufe an diese Funktionen ausgibt, und dann bindet unsere obige Integration diese Aufrufe an die Modul-ID. Damit die Laufzeit Zeichenfolgen wie "path/to/Button.js Button" empfängt, wenn eine Komponente registriert wird. (Oder im Fall von Webpack wären IDs Zahlen.) Vergessen Sie nicht, dass sowohl die Babel-Transformation als auch diese Umhüllung nur im Entwicklungsmodus erfolgen dürfen.

Als Alternative zum Umschließen des Modulcodes gibt es möglicherweise eine Möglichkeit, einen Versuch / Endlich wie diesen an der Stelle hinzuzufügen, an der der Bundler die Modulfabrik tatsächlich initialisiert. Wie hier in Metro (RN Bundler). Dies wäre wahrscheinlich besser, da wir nicht jedes Modul aufblähen oder uns Gedanken über die Einführung einer illegalen Syntax machen müssten, z. B. wenn wir import in try / finally .

Sobald Sie dies angeschlossen haben, haben Sie ein letztes Problem. Ihr Bundler weiß nicht, dass Sie die Updates bearbeiten, daher wird die Seite wahrscheinlich trotzdem neu geladen. Sie müssen es nicht sagen. Dies ist wiederum bündlerspezifisch, aber der von mir vorgeschlagene Ansatz besteht darin, zu überprüfen, ob alle Exporte React-Komponenten sind , und in diesem Fall das Update zu "akzeptieren". Im Webpack könnte es so aussehen:


// ...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();
}

Was ist isReactRefreshBoundary ? Es ist eine Sache, die Exporte flach auflistet und bestimmt, ob nur React-Komponenten exportiert werden. So entscheiden Sie, ob Sie ein Update akzeptieren oder nicht. Ich habe es hier nicht kopiert und eingefügt, aber diese Implementierung könnte ein guter Anfang sein. (In diesem Code bezieht sich Refresh auf react-refresh/runtime Export).

Sie möchten auch alle Exporte manuell registrieren, da die Babel-Transformation nur $RefreshReg$ für Funktionen aufruft. Wenn Sie dies nicht tun, werden Aktualisierungen von Klassen nicht erkannt.

Schließlich wäre die enqueueUpdate() -Funktion etwas, das von Modulen gemeinsam genutzt wird, die das eigentliche React-Update entprellen und ausführen.

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

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

Zu diesem Zeitpunkt sollte etwas funktionieren.

Nuancen

Es gibt einige grundlegende Erwartungen an die Erfahrung, die mir wichtig sind und die in das Branding "Fast Refresh" einfließen. Es sollte in der Lage sein, einen Syntaxfehler, einen Modulinitialisierungsfehler oder einen Renderfehler ordnungsgemäß zu beheben. Ich werde nicht im Detail auf diese Mechanismen eingehen, aber ich bin der festen Überzeugung, dass Sie Ihr Experiment nicht "Fast Refresh" nennen sollten, bis es diese Fälle gut behandelt.

Leider weiß ich nicht, ob Webpack all diese unterstützen kann, aber wir können um Hilfe bitten, wenn Sie in einen etwas funktionierenden Zustand geraten, aber dann stecken bleiben. Ich habe zum Beispiel festgestellt, dass die accept() API des Webpacks die Fehlerbehebung erschwert (Sie müssen nach einem Fehler eine vorherige Version des Moduls akzeptieren), aber es gibt eine Möglichkeit, dies zu umgehen. Eine andere Sache, auf die wir zurückkommen müssen, ist die automatische "Registrierung" aller Exporte und nicht nur der Exporte, die vom Babel-Plugin gefunden wurden. Lassen Sie uns dies zunächst ignorieren, aber wenn Sie etwas haben, das beispielsweise für ein Webpack funktioniert, kann ich mir das Polieren ansehen.

In ähnlicher Weise müssten wir es in eine "Fehlerbox" -Erfahrung integrieren, ähnlich wie react-error-overlay in Create React App. Das hat einige Nuancen, wie das Fehlerfeld verschwinden sollte, wenn Sie einen Fehler beheben. Das erfordert auch einige weitere Arbeiten, die wir durchführen können, sobald die Stiftung eingerichtet ist.

Lassen Sie mich wissen, wenn Sie Fragen haben!

Syntaxfehler / Initialisierungsfehler sollten "einfach genug" sein, um in irgendeiner Weise behandelt zu werden, bevor React angewiesen wird, ein Rendering zu starten. Wie würden Rendering-Fehler jedoch mit Fehlergrenzen interagieren?

Wenn ein Rendering-Fehler auftritt, wird die nächstgelegene Fehlergrenze ausgelöst, die sich selbst in einen Fehlerzustand versetzt, und es gibt keine generische Möglichkeit, Fehlergrenzen mitzuteilen, dass ihre untergeordneten Elemente nach einer Live-Aktualisierung auf magische Weise möglicherweise behoben wurden. Bekommt / sollte jede aktualisierbare Komponente kostenlos eine eigene Fehlergrenze erhalten, oder funktioniert die Fehlerbehandlung im Reconciler anders, wenn die Laufzeitunterstützung bei der Initialisierung erkannt wird?

Alle Exporte sind React-Komponenten und "akzeptieren" in diesem Fall das Update

Gibt es eine Möglichkeit, solche Komponenten zu erkennen? Soweit ich weiß - nein. Außer export.toString().indexOf('React')>0 , aber es würde aufhören, mit einem angewendeten HOC zu arbeiten.
Außerdem ist das Selbstakzeptieren am Ende der Datei nicht fehleranfällig - das neue Akzeptanzhandle würde nicht eingerichtet, und das nächste Update würde an die höhere Grenze sprudeln. Deshalb wurde require("react-hot-loader/root").hot erstellt.

In jedem Fall - es scheint so zu sein, als würde man den gesamten reaktionsspezifischen Code von react-hot-loader werfen und die externe API unberührt lassen - wäre dies ausreichend und auf alle vorhandenen Installationen anwendbar.

Die Verwendung von react-refresh/babel 0.4.0 gibt mir diesen Fehler bei einer großen Anzahl von Dateien:

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)

Ich habe diese Datei auf das Einfachste eingegrenzt, das sie verursacht:

import { useContext } from 'react'

export default () => useContext()

Wenn ein Rendering-Fehler auftritt, wird die nächstgelegene Fehlergrenze ausgelöst, die sich selbst in einen Fehlerzustand versetzt, und es gibt keine generische Möglichkeit, Fehlergrenzen mitzuteilen, dass ihre untergeordneten Elemente nach einer Live-Aktualisierung möglicherweise auf magische Weise behoben wurden.

Schneller Aktualisierungscode in React merkt sich, welche Grenzen derzeit fehlgeschlagen sind. Wenn ein Update für die schnelle Aktualisierung geplant ist, werden diese immer erneut bereitgestellt.

Wenn es keine Grenzen gibt, aber ein Root beim Update fehlgeschlagen ist, versucht Fast Refresh erneut, diesen Root mit seinem letzten Element zu rendern.

Wenn der Root beim Mounten fehlgeschlagen ist, werden Sie von runtime.hasUnrecoverableErrors() informiert. Dann müssen Sie ein Nachladen erzwingen. Wir könnten diesen Fall später bearbeiten, ich hatte noch keine Zeit, ihn zu beheben.

Die Verwendung von react-refresh / babel 0.4.0 gibt mir diesen Fehler bei einer großen Anzahl von Dateien:

Neue Ausgabe einreichen pls?

Gibt es eine Möglichkeit, solche Komponenten zu erkennen?

Ich habe eine Verknüpfung zu meiner Implementierung hergestellt, die selbst Runtime.isLikelyAReactComponent() . Es ist nicht perfekt, aber es ist gut genug.

Das neue Akzeptanzhandle würde nicht eingerichtet, und das nächste Update würde an die höhere Grenze sprudeln

Können Sie ein Beispiel geben? Ich folge nicht. Unabhängig davon ist das etwas Spezifisches für den Bündler. Ich habe Metro dazu gebracht, das zu tun, was ich wollte. Wir können das Webpack bitten, etwas hinzuzufügen, wenn eine API fehlt.

Ziel ist es, möglichst wenige Module erneut auszuführen und gleichzeitig die Konsistenz zu gewährleisten. Wir möchten für die meisten Änderungen keine Aktualisierungen im Stammverzeichnis vornehmen.

Es scheint so, als würde man den gesamten reaktionsspezifischen Code vom React-Hot-Loader abwerfen und die externe API unberührt lassen

Vielleicht, obwohl ich auch den Container der obersten Ebene entfernen möchte. Ich möchte auch eine engere Integration in die Fehlerbox. Vielleicht kann man das immer noch react-hot-loader .

Übrigens habe ich meinen Leitfaden so bearbeitet, dass er ein fehlendes Teil enthält, das ich vergessen habe - den Anruf performReactRefresh . Das ist die Sache, die Updates tatsächlich plant.

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

Ich würde mich mit einer solchen Logik nicht sicher fühlen. Auch wenn alle CapitalizedFunctions fast immer React-Komponenten sind - viele Module (von mir) haben auch andere Exporte. Zum Beispiel _exports-for-tests_. Das ist kein Problem, schafft aber eine gewisse Unvorhersehbarkeit - eine heiße Grenze kann an jedem Punkt erstellt werden ... oder nicht nach einem Zeilenwechsel.
Was könnte isLikelyComponentType Test brechen:

  • exportiert mapStateToProps (für Tests, die nicht in einem Produktionscode verwendet werden)
  • exportiert hook (und das ist in Ordnung)
  • exportiert Class was möglicherweise keine Reaktionsklasse ist (würde nicht, sollte aber)

Es würde also Fälle geben, in denen eine heiße Grenze festgelegt werden soll, aber nicht, und es würde Fälle geben, in denen eine heiße Grenze festgelegt wird, aber nicht. Klingt nach altem guten instabilen Hot-Reloading, das wir beide nicht ganz mögen :)

Es gibt einen Ort, an dem das Anwenden einer heißen Grenze nicht so unvorhersehbar wäre und durchaus zu erwarten wäre - eine Grenze von thing oder domain oder ein Verzeichnisindex, dh eine Wiederexportierung von index.js eine "öffentliche API" aus dem Component.js im selben Verzeichnis (kein Afaik im Facebook-Stil).

Mit anderen Worten - alles wie in Metro, jedoch mit weiteren Einschränkungen. Alles andere, wie die Flusenregel, nach der eine solche Grenze für eine verzögert geladene Komponente festgelegt wird, könnte verwendet werden, um das richtige Verhalten durchzusetzen.

Apropos - heiße schnelle Aktualisierung würde Lazy behandeln? Wird erwartet, dass es eine Grenze von der anderen Seite des import ?

Habe es kurz versucht, die Magie im Browser zu sehen und es ist so schön :) Ich habe das Einfachste getan, dh den gesamten Instrumentierungscode hartcodiert, also noch keine Webpack-Plugins

Kapture 2019-09-07 at 23 09 04

Repo hier: https://github.com/pekala/react-refresh-test

Nur neugierig, aber für Webpack, könnten Sie nicht einfach ein Babel-Plugin haben, um den Versuch / Endlich zu verpacken? Ich möchte nur sichergehen, dass mir nichts fehlt, bevor ich es probiere.

Das Babel-Plugin ist nicht umgebungsspezifisch. Ich würde es gerne so halten. Es weiß nichts über Module oder den Mechanismus zur Weitergabe von Updates. Diese unterscheiden sich je nach Bündler.

Zum Beispiel gibt es in Metro überhaupt keine Try / Final Wrapping-Transformation. Stattdessen setze ich try / finally in die Bundler-Laufzeit selbst, um die

Sie können natürlich ein anderes Babel-Plugin zum Verpacken erstellen. Aber das bringt dir nichts, wenn du das per Webpack machst. Da es sowieso webpackspezifisch ist. Und es kann verwirrend sein, dass Sie dieses Babel-Plugin versehentlich in einer anderen Umgebung (nicht im Webpack) ausführen könnten, in der es keinen Sinn ergibt.

Sie können, indem Sie sich in den Wasserfallhaken compilation.mainTemplate.hooks.require einhängen. Der vorherige Aufruf ist der Standardkörper der __webpack_require__ -Funktion, sodass Sie auf den Hook tippen können, um den Inhalt in einen try/finally -Block zu verpacken.

Das Problem besteht darin, einen Verweis auf React innerhalb von __webpack_require__ . Es ist möglich, erfordert jedoch möglicherweise ein gewisses Maß an Wiedereintritts- und Rekursionsschutz.

Weitere Informationen finden Sie im Webpack-Quellcode unter MainTemplate.js und web/JsonpMainTemplatePlugin.js . JsonpMainTemplatePlugin selbst klopft nur an ein paar Haken von MainTemplate.js , also ist das wahrscheinlich das "Fleisch", das Sie in Angriff nehmen müssen.

Hier ist ein hirnrissiger Prototyp, den ich zusammen gehackt habe und der effektiv das tut, was Dan oben skizziert hat. Es ist absolut unvollständig, beweist jedoch eine Lo-Fi-Implementierung im Webpack: https://gist.github.com/maisano/441a4bc6b2954205803d68deac04a716

Einige Notizen:

  • react-dom ist hier fest codiert, daher funktioniert dies nicht mit benutzerdefinierten Renderern oder Unterpaketen (z. B. react-dom/profiling ).
  • Ich habe nicht zu genau untersucht, wie alle Vorlagenvarianten von Webpack funktionieren, aber die Art und Weise, wie ich die Modulausführung verpackt habe, ist ziemlich hackig. Ich bin nicht sicher, ob dieses Beispiel funktionieren würde, wenn man beispielsweise das Bibliotheksziel umd verwendet.

Das Problem besteht darin, einen Verweis auf React im __webpack_require__ zu erhalten. Es ist möglich, erfordert jedoch möglicherweise ein gewisses Maß an Wiedereintritts- und Rekursionsschutz.

Ich gehe davon aus, dass Sie einen Verweis auf Refresh Runtime erhalten möchten.

In Metro habe ich das gelöst, indem ich so früh wie möglich require.Refresh = RefreshRuntime . Dann kann ich innerhalb der require -Implementierung eine Eigenschaft aus der require -Funktion selbst lesen. Es wird nicht sofort verfügbar sein, aber es spielt keine Rolle, ob wir es früh genug einstellen.

@maisano Ich musste eine Reihe von Dingen ändern, und letztendlich sehe ich die vom Webpack aufgerufene .accept-Funktion nicht. Ich habe sowohl .accept(module.i, () => {}) als auch .accept(() => {}) ausprobiert (selbstakzeptierend, außer dass dies im Webpack nicht funktioniert). Die hot -Eigenschaft ist aktiviert. Ich sehe, dass sie herunterfällt und akzeptierte Module durchläuft.

Also habe ich das Webpack gepatcht, um selbstakzeptierende Module aufzurufen, und das war die endgültige Lösung.

Hier ist der Patch:

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",

Ich weiß, dass dies gegen ihre API verstößt, die möchte, dass dies ein "errorCallback" ist. Ich erinnere mich, dass ich vor vielen Jahren bei der Arbeit an unserem internen HMR darauf gestoßen bin und letztendlich unseren eigenen Bundler geschrieben habe. Ich glaube, dass Paket die "selbstakzeptierende" Rückruf-API unterstützt. Vielleicht lohnt es sich, eine Ausgabe im Webpack zu öffnen und zu prüfen, ob wir sie zusammenführen können? @sokra

Also ... ich habe das Plugin basierend auf der Arbeit von @maisano weiter poliert:
https://github.com/pmmmwh/react-refresh-webpack-plugin
(Ich habe es in TypeScript geschrieben, weil ich nicht vertraue, dass ich zu Beginn mit Webpack-Interna herumgespielt habe. Ich kann das in einfaches JS / Flow konvertieren.)

Ich habe versucht, die Notwendigkeit eines Loaders für das Injizieren des Hot-Module-Codes mit Webpack Dependency -Klassen zu beseitigen, aber anscheinend erfordert dies eine erneute Analyse aller Module (denn selbst mit allen Funktionen inline benötigen wir noch eine Verweis auf react-refresh/runtime irgendwo).

Ein weiteres Problem ist, dass es keine einfachen Möglichkeiten (afaik) gibt, JavaScript-ähnliche Dateien im Webpack zu erkennen - zum Beispiel verwendet html-webpack-plugin auch den Typ javascript/auto , also habe ich das, was zu sein scheint, hartcodiert eine akzeptable Dateimaske (JS / TS / Flow) für die Loader-Injektion.

Ich habe auch eine Fehlerbehebung (zumindest einen Syntaxfehler) hinzugefügt, die auf einem Kommentar von @gaearon in diesem 5 Jahre alten Thread basiert. Als nächstes wird von Reaktionsfehlern wiederhergestellt - ich vermute, dass dies durch Einfügen einer globalen Fehlergrenze (ein bisschen wie AppWrapper von react-hot-loader ) erreicht werden kann, die auch die Fehlerbox-Schnittstelle in Angriff nimmt, dies aber nicht tat habe die Zeit, noch ganz darauf zu kommen.

Das von @natew aufgeworfene enqueueUpdate und des Anrufs hot.accpet(errorHandler) .

@pmmmwh Welches Timing! Ich habe gerade ein Repo erstellt, das ein wenig von der Arbeit, die ich im Kern geteilt hatte, aufbaute / optimierte.

Ich bin auf keinen Fall zur Fehlerbehandlung gekommen, obwohl das Plugin hier etwas solider ist als der ursprüngliche Ansatz, den ich gewählt hatte.

Als nächstes wird die Reaktion auf Reaktionsfehler behoben. Ich vermute, dass dies durch Einfügen einer globalen Fehlergrenze (ähnlich wie AppWrapper von React-Hot-Loader) erreicht werden kann, die auch die Fehlerbox-Schnittstelle in Angriff nimmt, aber nicht die Zeit hatte, darauf zuzugreifen das nur noch ganz.

Das sollte schon sofort funktionieren. Hier ist keine benutzerdefinierte Fehlergrenze oder Umbruch erforderlich.

Als nächstes wird die Reaktion auf Reaktionsfehler behoben. Ich vermute, dass dies durch Einfügen einer globalen Fehlergrenze (ähnlich wie AppWrapper von React-Hot-Loader) erreicht werden kann, die auch die Fehlerbox-Schnittstelle in Angriff nimmt, aber nicht die Zeit hatte, darauf zuzugreifen das nur noch ganz.

Das sollte schon sofort funktionieren. Hier ist keine benutzerdefinierte Fehlergrenze oder Umbruch erforderlich.

@gaearon Seltsam. Ich habe versucht, Fehler beim Rendern von Funktionskomponenten auszulösen. Wenn der Fehler in return auftritt, funktioniert HMR, aber wenn er woanders auftritt, funktioniert er manchmal nicht.

@pmmmwh Welches Timing! Ich habe gerade ein Repo erstellt, das ein wenig von der Arbeit, die ich im Kern geteilt hatte, aufbaute / optimierte.

Ich bin auf keinen Fall zur Fehlerbehandlung gekommen, obwohl das Plugin hier etwas solider ist als der ursprüngliche Ansatz, den ich gewählt hatte.

@maisano Was soll ich sagen? Ich habe tatsächlich angefangen, daran zu arbeiten, und bin letztes Wochenende mit dem Problem der Abhängigkeitsinjektion festgefahren ... Ihr Kern hat mir den Ausweg gegeben: tada:

Wenn der Fehler im Gegenzug auftritt, funktioniert HMR, aber wenn er woanders auftritt, funktioniert er manchmal nicht.

Ich würde mehr Details darüber benötigen, was Sie genau versucht haben und was Sie unter "funktioniert" und "funktioniert nicht" verstehen.

Es gibt viele Dinge, die schief gehen können, wenn die Integration des Modulbündlers nicht korrekt implementiert ist (das ist das Thema oder dieser Thread). Ich würde erwarten, dass nichts in React selbst die Wiederherstellung nach Fehlern verhindert, die während der Bearbeitung auftreten. Sie können überprüfen, ob es in React Native 0.61 RC3 funktioniert.

@pmmmwh , @maisano Die folgende Prüfung überspringt Module mit Komponenten als benannte Exporte und es wird keine Aktualisierungsgrenze festgelegt:

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;
}

Ich weiß nicht, warum dies in Metro erforderlich ist, aber in webpack Getter nur die genannten Exporte zurück und soweit ich das beurteilen kann, gibt es keine Nebenwirkungen. Es sollte also sicher zu entfernen sein.

@gaearon React.lazy -Komponenten (z. B. Code-Split-Routen) werden nicht neu gerendert. Ist das beabsichtigt? Ich kann sehen, dass die Aktualisierungsgrenze festgelegt ist, aber performReactRefresh() scheint nichts zu tun. Änderungen an faulen Kindern werden gut aktualisiert, daher ist dies keine große Sache, aber ich frage mich, ob wir etwas falsch machen ...

lazy ist eine kleine Zustandsmaschine - sie enthält einen Verweis auf die alte Komponente, und dieser Verweis muss aktualisiert werden.
Stellen wir uns nun vor, es war und bezieht sich jetzt auf ein brandneues lazy -Objekt. Es muss erneut an die loading -Phase gedacht werden, und das würde wahrscheinlich alle verschachtelten Bäume zerstören.

Ich würde erwarten, dass faul arbeitet. Vielleicht ist etwas kaputt gegangen. Ich muss einen reproduzierenden Fall sehen.

Da es einige Prototypen gab, sollten wir einen auswählen und diese Diskussion dann auf ihre Themen verlagern? Und dort iterieren.

Es gibt:
https://github.com/maisano/react-refresh-plugin
und:
https://github.com/pmmmwh/react-refresh-webpack-plugin
Ich habe eine Abzweigung des Plugins von pmmmwh eingerichtet, das mit [email protected] funktioniert (behebt auch benannte Exporte):
https://github.com/WebHotelier/webpack-fast-refresh

Was ist mit react-hot-loader ?

react-hot-loader fast alle Funktionen von fast refresh zurückportiert, aber es gibt nur wenige historische und integrative Momente, in denen nicht alle Funktionen zurückportiert werden können, und ehrlich gesagt macht es keinen Sinn, sie in "rhl" -Begriffen neu zu implementieren . Also - lass es in Rente gehen.

Ich würde mehr Details darüber benötigen, was Sie genau versucht haben und was Sie unter "funktioniert" und "funktioniert nicht" verstehen.

Es gibt viele Dinge, die schief gehen können, wenn die Integration des Modulbündlers nicht korrekt implementiert ist (das ist das Thema oder dieser Thread). Ich würde erwarten, dass nichts in React selbst die Wiederherstellung nach Fehlern verhindert, die während der Bearbeitung auftreten. Sie können überprüfen, ob es in React Native 0.61 RC3 funktioniert.

Nach ein paar Änderungen kann ich überprüfen, ob es funktioniert.

Es scheint jedoch, dass das Babel-Plugin nicht für Klassen funktioniert hat. Ich habe dies überprüft und dies scheint unabhängig von der Implementierung zu geschehen, da der gesamte injizierte Code und react-refresh/runtime ordnungsgemäß funktionieren. Ich bin mir nicht sicher, ob dies beabsichtigt ist oder ob es sich um ein Webpack handelt. Wenn es das letztere ist, kann ich versuchen, morgen einen Fix zu finden. (Ich habe dies auch nur mit der U-Bahn-Voreinstellung getestet, reproduzieren Sie das Wesentliche hier )

Ich bin mir ziemlich sicher, dass es für RN funktioniert, aber auf meinem aktuellen Computer steht mir keine Umgebung zum Testen auf RN zur Verfügung. Wenn Sie mich also auf die Implementierung des Babel-Plugins in der Metro oder auf die Transformationen hinweisen können, wäre das wirklich hilfreich.

Da es einige Prototypen gab, sollten wir einen auswählen und diese Diskussion dann auf ihre Themen verlagern? Und dort iterieren.

Vielleicht können wir hier hingehen? Seit meinem letzten Kommentar habe ich das gesamte Projekt in einfaches JS portiert und einige Korrekturen für die Update-Warteschlange hinzugefügt. Ich muss das Plugin für Webpack @ 5 nicht @apostolos und die neue HMR-Logik in Webpack @ next gelesen habe , sollten die Korrekturen einfach sein.

Ja, das Babel-Plugin registriert keine Klassen. Dies soll auf der Ebene des Modulsystems geschehen. Jeder Export sollte auf "wahrscheinlich" als Reaktionskomponente überprüft werden. (Die Laufzeit bietet eine Überprüfungsfunktion.) Wenn true, registrieren Sie den Export genau wie das Babel-Plugin. Geben Sie ihm eine ID wie filename exports%export_name . Dies ist der Grund, warum Klassen in Metro funktionieren, da das Babel-Plugin sie nicht findet.

Mit anderen Worten, da wir den Klassenstatus sowieso nicht beibehalten können, können wir sie genauso gut der Exportgrenze des Moduls anvertrauen, anstatt zu versuchen, sie mit einer Transformation inline im Quellcode zu finden. Exporte sollten als "Allheilmittel" für Komponenten dienen, die wir mit dem Plugin nicht gefunden haben.

Mailchimp hat angefangen, eine Abzweigung des Plugins zu verwenden, das ich zuletzt geteilt habe. Es wurde ein bisschen mehr ausgearbeitet und Leute, die sich dafür entschieden haben, es zu benutzen, scheinen ziemlich glücklich zu sein. Wir werden es weiterhin lokal wiederholen. Ich habe vor, die Abzweigung zu entfernen und Updates stromaufwärts zu veröffentlichen, sobald es etwas weiter ist.

@gaearon Gedanken zum Hinzufügen eines Symbols, die wir an Dinge anhängen können, von denen wir wissen, dass sie sicher

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

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

Wobei create nur ein Objekt zurückgibt. Ich habe es vorerst gepatcht, aber wir könnten dem Standardexportobjekt dort leicht ein Symbol hinzufügen, das angibt, dass es sich um eine sichere Datei handelt. Ich bin mir nicht sicher, welches Muster genau das beste ist.

Bearbeiten: Ich habe erkannt, dass dies in die Aktualisierungsimplementierung gehen kann! Ich dachte, es könnte zur Laufzeit besser sein, aber vielleicht auch nicht. Bei so vielen verschiedenen Geräten des Laders kann es besser sein, einen Standardweg zu haben.

Lassen Sie uns 10 Jahre vorwärts. Wie sieht Ihre Codebasis aus? Hier zulassen, dort nicht zulassen? Wie halte ich diese Flaggen auf dem neuesten Stand? Wie kann man darüber nachdenken? Als ob es _sichere zu aktualisierende_ Speicherorte und _unsafe_ gibt, müssen Sie diese beibehalten oder können sie aus irgendeinem Grund nicht richtig abgleichen. Welche Gründe sind jeweils gültige Gründe?

  • welche symbols Sie mehr haben werden - ungefähr force allow reload oder force disallow reload
  • Warum möchten Sie möglicherweise die Aktualisierungsverbreitungsgrenze senken (dh das Update an "dieser" Modulgrenze akzeptieren) oder es erhöhen (dh das Update an "dieser" Modulgrenze akzeptieren)?
  • Was würde passieren, wenn keine Grenzen gesetzt würden? Ist es nur ein Leistungsproblem oder könnte etwas Schwerwiegenderes passieren?

Hallo Leute 👋 Ich möchte hier mithelfen. Haben wir uns auf ein einziges Repo / eine einzige Anstrengung geeinigt?

Ist es dieses Repo, das von @pmmmwh geteilt wird?
https://github.com/pmmmwh/react-refresh-webpack-plugin

Oder ist es dieses Repo, das von @maisano geteilt wird?
https://github.com/maisano/react-refresh-plugin

Sieht so aus, als ob die von @pmmmwh in jüngerer Zeit festgelegt wurde. Wenn ich nichts anderes höre, gehe ich davon aus, dass ich mich darauf konzentrieren muss.

Die Implementierung in Paket 2 hat hier begonnen: https://github.com/parcel-bundler/parcel/pull/3654

Sommer!

Für alle, die danach suchen, eine Implementierung von React Refresh for Rollup-Projekten mit Nollup für die Entwicklung: https://github.com/PepsRyuu/rollup-plugin-react-refresh

Wahrscheinlich nicht die sauberste Implementierung, aber es funktioniert.

Für Webpack-Lösungen scheint es keine offizielle Veröffentlichung der oben genannten Plugins gegeben zu haben. Daher scheint die beste HMR-Lösung für Reaktionen die Bibliothek von Dan hier zu sein: https://github.com/gaearon/react-hot-loader

Wir haben gerade Parcel 2 alpha 3 mit Unterstützung für Fast Refresh ausgeliefert! Probieren Sie es einfach aus. 😍 https://twitter.com/devongovett/status/1197187388985860096?s=20

🥳 Abschreibungshinweis zu RHL hinzugefügt 🥳

Ein Rezept, mit dem ich dies an CRA-Apps mit @pmmmwhs Arbeit in Arbeit ausprobiert habe : react-app-rewired und 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

Bearbeiten Sie ./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 Datei hinzufügen:

// 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,
);

Genieße die Erfahrung bisher. Vielen Dank für die Arbeit aller Beteiligten!

Danke @ drather19 !

Ich habe ein Repository basierend auf Ihrer Anweisung erstellt. Es funktioniert:https://github.com/jihchi/react-app-rewired-react-refreshWenn jemand es versuchen und etwas tippen möchte, können Sie das Repo klonen.


Weitere Informationen finden Sie unter https://github.com/pmmmwh/react-refresh-webpack-plugin/tree/master/examples/cra-kitchen-sink

UND ... v0.1.0 für Webpack wird nur ausgeliefert 🎉

@ drather19 @jihchi
Ihr wollt vielleicht auf diese Version umsteigen - sie beinhaltet eine einheitlichere Erfahrung sowie viele Fehlerkorrekturen bei der ersten Implementierung.

@pmmmwh unterstützt ts-loader + babel-loader ?

@pmmmwh unterstützt ts-loader + babel-loader ?

Ich habe nur mit Babel gegen TS getestet und es funktioniert. Wenn es also nicht funktioniert, wenn Sie ts + babel-Lader verwenden, können Sie gerne ein Problem einreichen :)

@ drather19 Ich habe versucht, dein Repo zu klonen und

Umgebung,
OS - OSX 10.14.6
Knoten - v12.13.0
Garn -1,19,2

@pmmmwh - FYI

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 Ich habe versucht, dein Repo zu klonen und

Umgebung,
OS - OSX 10.14.6
Knoten - v12.13.0
Garn -1,19,2

@pmmmwh - FYI

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

Dies ist in der master -Zweigstelle des Plugins behoben und wird morgen veröffentlicht.

Ich habe es geschafft, das Webpack -Plugin von @pmmmwh mit einer TypeScript React-App unter Verwendung von babel zum

@IronSean Bitte melden Sie es im Repo dieses Plugins? Das klingt nicht normal.

Ich werde weiter damit spielen, um zu sehen, ob mir auf der Babel-Konfigurationsseite etwas fehlt, das die Leistung näher bringt, aber im Moment ist es eine Wäsche im Vergleich zu Ts-Loader und vollständigen Aktualisierungen.

Hast du etwas dagegen, deine Konfiguration / dein Setup dort zu veröffentlichen? Ich werde die Probleme ohne mehr Kontext nicht herausfinden können.

@pmmmwh Ich habe diese Ausgabe geöffnet, um die Diskussion in Ihr Repo zu verschieben, nachdem ich bestätigt habe, dass tatsächlich Ihr Plugin den Unterschied
https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/20

Funktioniert react-refresh ( React Fast Refresh ?) Mit Preact oder ist react-hot-loader die langfristige Lösung für Preact?

@Jumblemuddle , das von

Für CRA-Leute, die mit Fast Refresh arbeiten möchten, hatte ich jetzt über die folgenden craco.config.js mehr Glück mit craco (vs. React-App-Rewired + Customize-Cra):

// 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 })],
        [],
      ),
    ],
  },
};

Insbesondere durch Hinzufügen von webpackConfig.optimization.runtimeChunk = false; können Sie Hooks hinzufügen / entfernen und trotzdem schnell aktualisieren.

Genießen Sie das verbesserte Erlebnis jetzt noch mehr. Vielen Dank an @ mmhand123 für den Tipp über https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/25! (<- gelöst!)

Basierend auf dem Vorschlag von @ drather19 habe ich ein Customize-Cra-Plugin veröffentlicht, um es einfacher zu machen. Siehe esetnik / customize-cra-react-refresh .

Dank @ drather19 ändere ich den Code leicht, jetzt kann er in einem Monorepo-Setup des Garnarbeitsbereichs funktionieren.

Installieren Sie zunächst Folgendes in den Unterpaketen, die Sie für die schnelle Aktualisierung aktivieren möchten:

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

Fügen Sie dies dann zu 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 Erwarten wir, dass Fast Refresh zu einem bestimmten Zeitpunkt standardmäßig in der CRA verfügbar sein wird?
wenn ja, was ist dafür erforderlich?

Dafür ist ein gewisser Arbeitsaufwand erforderlich :-), der gerade erledigt wird.

Wird die Verwendung von HMR-Funktionen aufgerufen? Zum Beispiel componentDidMount.
Ich benutze React-Proxy und componentDidMount wird aufgerufen.
Und reagieren 15.X kann Fast Refresh verwenden?

  • componentDidMount wird aufgerufen. Sowie unmount - Klassen würden vollständig neu geladen.
  • und es ist ein guter Zeitpunkt, die Verwendung von react-proxy . Nun, Sie sollten wahrscheinlich vor ein paar Jahren aufhören.
  • 15.X ? - absolut nicht. Schnelle Aktualisierung ist ein Teil der Reaktion und existiert daher nur in modernen Versionen.

Also sollten wir Fast Refresh oder React-Hot-Loader verwenden, um React-Proxy zu ersetzen?
Gibt es eine Möglichkeit zu verhindern, dass Funktionen (componentDidMount) für HMR ausgeführt werden? - Es wird eine Methode aufgerufen, um neue Daten abzurufen.

Wie soll ich React-Hot-Loader in JIT verwenden? - Browser-Echtzeit-Kompilierung

  • Also sollten wir Fast Refresh oder React-Hot-Loader verwenden, um React-Proxy zu ersetzen?

    Versuchen Sie zuerst fast refresh , dann RHL

  • Gibt es eine Möglichkeit zu verhindern, dass Funktionen (componentDidMount) für HMR ausgeführt werden? - Es wird eine Methode aufgerufen, um neue Daten abzurufen.

    (Verwenden Sie Hooks ...), verlassen Sie sich nicht auf den Komponentenlebenszyklus und rufen Sie die Daten bei Bedarf ab. Probieren Sie react-query , swr oder andere Lösungen aus.

Was die Frage betrifft, wie das neue HMR verwendet werden soll, glaube ich nicht, dass ich die neuesten Überlegungen dort kenne. Ich sehe, dass @gaearon eine Wip-PR auf dem CRA-Repo hat:

Um den Lesern zu verdeutlichen, dass PR sehr veraltet und nicht mehr relevant ist.

Ich muss etwas darüber aufschreiben, wie Fast Refresh funktioniert und wie es integriert wird. Ich hatte noch keine Zeit.

Bis heute ist diese PR noch offen. Es wäre schön, wenn nur relevante PRs, die noch eine Chance auf Zusammenführung haben, offen gehalten würden, um einen besseren Überblick zu erhalten. Wenn Sie sie nur als Referenz behalten, würde ich empfehlen, Inhalte in einen Zweig, ein Tag oder ein anderes Repository zu verschieben.

Ich bekomme immer wieder Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly. Ich habe die Dokumentation befolgt, aber anscheinend habe ich etwas verpasst?

Ich erhalte immer wieder den Fehler: [React Refresh] Hot Module Replacement (HMR) ist nicht aktiviert! React Refresh erfordert, dass HMR ordnungsgemäß funktioniert. Ich habe die Dokumentation befolgt, aber anscheinend habe ich etwas verpasst?

@silkfire Ich gehe davon aus, dass Sie das Webpack-Plugin verwenden. Wenn ja, legen Sie Ihre Frage bitte im Webpack-Plugin-Repo ab: https://github.com/pmmmwh/react-refresh-webpack-plugin/.

Bis heute ist diese PR noch offen. Es wäre schön, wenn nur relevante PRs, die noch eine Chance auf Zusammenführung haben, offen gehalten würden, um einen besseren Überblick zu erhalten. Wenn Sie sie nur als Referenz behalten, würde ich empfehlen, Inhalte in einen Zweig, ein Tag oder ein anderes Repository zu verschieben.

Ich schätze Ihren Vorschlag, aber bei Tausenden von ungelesenen Benachrichtigungen kann es manchmal schwierig für mich sein, mich daran zu erinnern, alte PRs erneut zu besuchen. Ich vertraue darauf, dass die Repository-Betreuer von Create React App das Richtige tun und schließen, wenn sie dies für nicht mehr nützlich halten.

Ich werde das schließen.

Wir haben https://github.com/pmmmwh/react-refresh-webpack-plugin/ als Referenzimplementierung für Webpack.
Unter https://github.com/facebook/react/issues/16604#issuecomment -528663101 wird erläutert, wie eine benutzerdefinierte Integration vorgenommen wird.

Ich bekomme immer wieder Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly. Ich habe die Dokumentation befolgt, aber anscheinend habe ich etwas verpasst?

Anscheinend haben Sie das Webpack HMR nicht aktiviert. Für weitere Hilfe melden Sie bitte ein Problem im Repo des Plugins.

Da Hot Replacement jetzt Teil von React ist, sollte es einen separaten Platz in der React-Dokumentation haben, der auf die zusätzlichen Bibliotheken verweist, die mit bestimmten Bundlern und Plattformen verwendet werden sollen, und einige noch vorhandene Fallstricke wie bei selbstaktualisierenden CSS erklärt Module.

Informationen wie diese sollten nicht in Github-Problemen und Blog-Posts vergraben werden.

@theKashey ist in React, aber die React-Dom-Implementierung ist zum einen nur experimentell.
Es gibt auch eine schnelle Aktualisierungsimplementierung, die mit der Create-React-App gebündelt wird, aber noch nicht veröffentlicht wurde: pmmmwh / react-refresh-webpack-plugin # 7. Vielleicht wird es in der nächsten React-Scripts-Version sein.

Daher hält es das React-Team derzeit wahrscheinlich nicht für richtig, in dieser experimentellen Phase über Fast Refresh for React-Dom zu sprechen.

Es ist in React, aber die React-Dom-Implementierung ist zum einen nur experimentell.

Um klar zu sein, ist die Implementierung in react-dom selbst stabil, genau wie in React Native. Es ist nur so, dass die Integrationen nicht alle stabil sind.

sollte es einen separaten Platz in der React-Dokumentation haben, der auf die zusätzlichen Bibliotheken verweist, die mit bestimmten Bundlern und Plattformen verwendet werden sollen, und einige noch vorhandene Fallstricke erklärt, wie bei selbstaktualisierenden CSS-Modulen.

Das klingt vernünftig. Gerne nehme ich eine PR, die dem Abschnitt "Erweiterte Anleitungen" hinzugefügt wird, möglicherweise basierend auf einer ähnlichen RN-Seite .

@gaearon
Meine Reaktions-App ist mit einigen gestalteten Komponentenänderungen einverstanden und wendet diese Änderungen ohne Probleme korrekt an.
Wenn ich jedoch Code in einem Redux-Reduzierer ändere, wird die gesamte App hart aktualisiert und verliert alle Redux-Zustände.
Muss ich einige andere Bibliotheken wie redux-persist , um den aktuellen Status zusammen mit react-fast-refresh zu speichern?

Wir haben den Kreis geschlossen und jetzt geht es wieder los 😅

So funktioniert Low-Level-HMR und liegt außerhalb der Verantwortung für eine schnelle Aktualisierung. Bitte beziehen Sie sich auf Redux- oder Webpack-Dokumente

Wir haben den Kreis geschlossen und jetzt geht es wieder los 😅

So funktioniert Low-Level-HMR und liegt außerhalb der Verantwortung für eine schnelle Aktualisierung. Bitte beziehen Sie sich auf Redux- oder Webpack-Dokumente

Würden Sie die Vollkreisreferenz verknüpfen?

@ jwchang0206 Stellen Sie sicher , Code wie haben dies in Ihrem Geschäft.

Würden Sie die Vollkreisreferenz verknüpfen?

Die gleichen Fragen wurden für React Hot Loader gestellt. Die gleichen Antworten wurden gegeben. Wir stehen am Anfang eines neuen Zyklus.

@ jwchang0206 Schauen Sie sich Redux-Reducers-Injector an , eine kleine Bibliothek, die ich geschrieben habe, um dieses Problem zu beheben .
Damit können Sie das Nachladen von Reduzierstücken mit heißem Nachladen unterstützen.
Stellen Sie sicher, dass Sie die Redux-Prinzipien der Unveränderlichkeit in Ihren Reduzierern befolgen und es funktioniert reibungslos 💯
Und wenn Sie Sagen verwenden, können Sie Redux-Sagen-Injektor verwenden .

@gaearon Ich bin ein bisschen verwirrt von der Verwendung von window . Es sieht für mich nicht so aus, als ob es wirklich notwendig wäre, weil die Implementierung ausgetauscht wird? Was ist der Sinn davon?

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
}

Ich habe meinen eigenen benutzerdefinierten Bundler und bin dabei, dies zu implementieren, aber ich kann nicht verstehen, warum dies ein absolutes Muss wäre oder wozu es gut wäre ... anfangs vermutete ich eine Optimierung der Speichernutzung / -leckage, aber diese werden nur Anrufe an die RefreshRuntime ...

@leidegre Ich kann die Entscheidung, $ RefreshSig $ für das Fensterobjekt @pmmmwh kam zur Rettung, indem er sein Fast Refresh Webpack-Plugin anpasste, um die Kopplung von Fast Refresh an den Browser zu überwinden (aufgetretene und überwundene Probleme wurden in diesem Thread behandelt: https://github.com/pmmmwh/react-refresh-webpack-plugin/ Fragen / 79). Ich frage mich, ob der verwendete Ansatz für Sie bei der Integration von Fast Refresh durch Ihren benutzerdefinierten Bundler von Nutzen ist.

Mein Bundler ist meistens ein Wrapper um den TypeScript-Compiler. Die Implementierung ist meistens diese, angepasst vom react-refresh/babel Besucher.

Dies ist nur eine einfache Sache, die funktioniert, aber nicht so vollständig ist wie der react-refresh/bable Besucher.

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
}

Ich war mir zunächst nicht sicher, wie ich createSignatureFunctionForTransform aber es ist nur eine Factory-Funktion, die für jede Komponente eine kleine Zustandsmaschine erstellt. Sie rufen es also einmal für jede Funktion mit der statischen Hook-Signatur auf (die nur ein undurchsichtiger Wert ist, ähnlich einem Hash). Sie rufen es dann vom Rendern auf, damit es die Setup-Arbeit beendet.

Es ändert sich ungefähr so:

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>
  )
}

Das sehr gut finden:

"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();

Beachten Sie, dass der Besucher unvollständig ist. Es wird nur der grundlegendste Anwendungsfall behandelt.

Ich bin ein bisschen verwirrt von der Verwendung von window . Es sieht für mich nicht so aus, als ob es wirklich notwendig wäre, weil die Implementierung ausgetauscht wird? Was ist der Sinn davon?

@leidegre

Ich denke, die Implementierung in Metro verwendet nicht window , sondern den tatsächlichen Umfang von global .

Ich weiß nichts über die ursprüngliche Begründung dieser Implementierung, aber sie war aus meiner Erfahrung heraus nützlich - sie stellt sicher, dass die tatsächliche Anforderungslogik unabhängig von der schnellen Aktualisierungslogik ist (was bedeutet, dass react-refresh/babel -Transformationen mit praktisch jeder verwendet werden können Bündler). Wie beim Austausch fungiert es auch als Schutz, um sicherzustellen, dass Module, die nicht zur Laufzeit verarbeitet werden sollen, nicht verarbeitet werden:

Stellen Sie sich einen Fall vor, in dem @babel/runtime verwendet wird, wodurch Helfer als Importe in das Bundle eingefügt werden und Sie nur HMR-Code ohne node_modules verwenden möchten. Wenn Sie leere Helfer nicht zuerst initialisieren und dennoch dem globalen Bereich Helfer zuweisen, kann es in seltenen Fällen vorkommen, dass die mit Babel injizierten Helfer cleanup aufrufen, bevor das User-Land-Modul die Initialisierung tatsächlich abschließt (weil sie untergeordnet sind) Importe).

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen