React: ¿Cómo deberíamos configurar aplicaciones para HMR ahora que Fast Refresh reemplaza react-hot-loader?

Creado en 29 ago. 2019  ·  85Comentarios  ·  Fuente: facebook/react

Dan Abramov mencionó que Devtools v4 hará que react-hot-loader obsoleto: https://twitter.com/dan_abramov/status/1144715740983046144?s=20

Yo:
Tengo este gancho:
require("react-reconciler")(hostConfig).injectIntoDevTools(opts);
Pero HMR siempre ha funcionado completamente sin él. ¿Es este ahora un nuevo requisito?

Dan:
Sí, eso es lo que usa el nuevo mecanismo. El nuevo mecanismo no necesita "react-hot-loader", por lo que para cuando actualice, querrá eliminar ese paquete. (Es bastante invasivo)

Sin embargo, no veo ninguna mención de HMR en la documentación de Devtools; ahora que react-hot-loader ha vuelto obsoleto (y con él, el método require("react-hot-loader/root").hot ), ¿cómo deberíamos configurar aplicaciones para HMR en:

  • Reaccionar aplicaciones DOM
  • Reaccionar aplicaciones nativas
  • Reaccionar aplicaciones de renderizado personalizadas

Me interesaría particularmente una guía de migración específicamente para cualquiera que ya haya configurado HMR a través de react-hot-loader .

Además, para HMR, ¿importa si estamos usando Devtools independientes o Devtools de extensión del navegador?

Question

Comentario más útil

Bien, aquí va.

¿Qué es la actualización rápida?

Es una reimplementación de la "recarga en caliente" con el apoyo total de React. Originalmente se envía para React Native, pero la mayor parte de la implementación es independiente de la plataforma. El plan es usarlo en todos los ámbitos, como un reemplazo de las soluciones puramente para el usuario (como react-hot-loader ).

¿Puedo utilizar Fast Refresh en la Web?

Teóricamente, sí, ese es el plan. Prácticamente, alguien necesita integrarlo con los paquetes comunes en la web (por ejemplo, Webpack, Parcel). No he llegado a hacer eso todavía. Quizás alguien quiera recogerlo. Este comentario es una guía aproximada de cómo lo haría.

¿En qué consiste?

Fast Refresh se basa en varias piezas que trabajan juntas:

  • Mecanismo de "sustitución de módulo en caliente" en el sistema de módulos.

    • Por lo general, también lo proporciona el paquete.

    • Por ejemplo, en el paquete web, module.hot API le permite hacer esto.

  • React renderizador 16.9.0+ (por ejemplo, React DOM 16.9)
  • react-refresh/runtime punto de entrada
  • react-refresh/babel Complemento

Probablemente querrá trabajar en la parte de integración. Es decir, integrar react-refresh/runtime con el mecanismo de "reemplazo de módulo en caliente" de Webpack.

¿Cómo es la integración?

⚠️⚠️⚠️ PARA SER CLAROS, ESTA ES UNA GUÍA PARA LAS PERSONAS QUE QUIEREN IMPLEMENTAR LA INTEGRACIÓN POR SÍ MISMAS. ¡PROCEDA BAJO SU PROPIA PRECAUCIÓN!

Hay algunas cosas que desea hacer mínimamente:

  • Habilite HMR en su paquete (por ejemplo, paquete web)
  • Asegúrese de que React sea 16.9.0+
  • Agregue react-refresh/babel a sus complementos de Babel

En ese momento, su aplicación debería fallar. Debe contener llamadas a funciones $RefreshReg$ y $RefreshSig$ que no están definidas.

Luego, debe crear un nuevo punto de entrada JS que debe ejecutarse antes que cualquier código en su aplicación , incluido react-dom (!) Esto es importante; si se ejecuta después de react-dom , nada funcionará. Ese punto de entrada debería hacer algo como esto:

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

Esto debería solucionar los fallos. Pero aún así no hará nada porque estas implementaciones $RefreshReg$ y $RefreshSig$ son ningún error. Conectarlos es la parte principal del trabajo de integración que necesita hacer.

La forma de hacerlo depende de su agrupador. Supongo que con el paquete web podrías escribir un cargador que agregue código antes y después de que se ejecute cada módulo. O tal vez haya algún gancho para inyectar algo en la plantilla del módulo. Independientemente, lo que desea lograr es que cada módulo se vea así:

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

La idea aquí es que nuestro complemento de Babel emite llamadas a estas funciones, y luego nuestra integración anterior vincula esas llamadas al ID del módulo. Para que el tiempo de ejecución reciba cadenas como "path/to/Button.js Button" cuando se registra un componente. (O, en el caso del paquete web, los ID serían números). No olvide que tanto la transformación de Babel como esta envoltura solo deben ocurrir en el modo de desarrollo.

Como alternativa a envolver el código del módulo, tal vez haya alguna forma de agregar un try / finalmente como este en el lugar donde el empaquetado realmente inicializa la fábrica de módulos. Como lo hacemos aquí en Metro (RN bundler). Esto probablemente sería mejor porque no necesitaríamos sobrecargar cada módulo, o preocuparnos por introducir una sintaxis ilegal, por ejemplo, al envolver import con try / finally .

Una vez que conecte esto, tendrá un último problema. Su agrupador no sabe que está manejando las actualizaciones, por lo que probablemente vuelva a cargar la página de todos modos. Tienes que decirle que no lo haga. De nuevo, esto es específico del paquete, pero el enfoque que sugiero es verificar si todas las exportaciones son componentes de React y, en ese caso, "aceptar" la actualización. En webpack podría verse como algo:


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

¿Qué es isReactRefreshBoundary ? Es algo que enumera las exportaciones de manera superficial y determina si solo exporta componentes de React. Así es como decides si aceptar una actualización o no. No copié y pegué aquí, pero esta implementación podría ser un buen comienzo. (En ese código, Refresh refiere a react-refresh/runtime exportación).

También querrá registrar manualmente todas las exportaciones porque la transformación de Babel solo llamará $RefreshReg$ para funciones. Si no lo hace, no se detectarán las actualizaciones de las clases.

Finalmente, la función enqueueUpdate() sería algo compartido entre módulos que elimina los rebotes y realiza la actualización de React real.

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

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

En este punto, debería tener algo funcionando.

Matices

Hay algunas expectativas de experiencia de base que me importan y que se refieren a la marca "Actualización rápida". Debería poder recuperarse sin problemas de un error de sintaxis, un error de inicialización del módulo o un error de representación. No entraré en estos mecanismos en detalle, pero creo firmemente que no debería llamar a su experimento "Actualización rápida" hasta que maneje bien esos casos.

Desafortunadamente, no sé si el paquete web puede admitir todos esos, pero podemos pedir ayuda si llega a un estado de funcionamiento pero luego se queda atascado. Por ejemplo, he notado que la API accept() webpack hace que la recuperación de errores sea más difícil (debe aceptar una versión _previa_ del módulo después de un error), pero hay una manera de solucionarlo. Otra cosa a la que tendremos que volver es a "registrar" automáticamente todas las exportaciones, y no solo las encontradas por el complemento de Babel. Por ahora, ignoremos esto, pero si tiene algo que funcione, por ejemplo, webpack, puedo buscar pulirlo.

De manera similar, necesitaríamos integrarlo con una experiencia de "cuadro de error", similar a react-error-overlay que tenemos en la aplicación Create React. Eso tiene algunos matices, como que el cuadro de error debería desaparecer cuando corrige un error. Eso también requiere más trabajo que podemos hacer una vez que la base esté en su lugar.

¡Hazme saber si tienes alguna pregunta!

Todos 85 comentarios

Hay algo de confusión. Las nuevas DevTools no permiten la recarga en caliente (ni tienen nada que ver con la recarga). Más bien, los cambios de recarga en caliente en los que Dan ha estado trabajando hacen uso del "gancho" que DevTools y React usan para comunicarse. Se agrega a sí mismo en el medio para que pueda recargar.

Edité el título para eliminar la mención de DevTools (ya que puede causar confusión).

En cuanto a la pregunta sobre cómo se debe usar el nuevo HMR, no creo que conozca las últimas ideas allí. Veo que @gaearon tiene un wip PR en el repositorio de CRA:
https://github.com/facebook/create-react-app/pull/5958

En cuanto a la pregunta sobre cómo se debe usar el nuevo HMR, no creo que conozca las últimas ideas allí. Veo que @gaearon tiene un wip PR en el repositorio de CRA:

Para aclarar a los lectores, que las relaciones públicas están muy desactualizadas y ya no son relevantes.


Necesito escribir algo sobre cómo funciona Fast Refresh y cómo integrarlo. Todavía no he tenido tiempo.

Bien, aquí va.

¿Qué es la actualización rápida?

Es una reimplementación de la "recarga en caliente" con el apoyo total de React. Originalmente se envía para React Native, pero la mayor parte de la implementación es independiente de la plataforma. El plan es usarlo en todos los ámbitos, como un reemplazo de las soluciones puramente para el usuario (como react-hot-loader ).

¿Puedo utilizar Fast Refresh en la Web?

Teóricamente, sí, ese es el plan. Prácticamente, alguien necesita integrarlo con los paquetes comunes en la web (por ejemplo, Webpack, Parcel). No he llegado a hacer eso todavía. Quizás alguien quiera recogerlo. Este comentario es una guía aproximada de cómo lo haría.

¿En qué consiste?

Fast Refresh se basa en varias piezas que trabajan juntas:

  • Mecanismo de "sustitución de módulo en caliente" en el sistema de módulos.

    • Por lo general, también lo proporciona el paquete.

    • Por ejemplo, en el paquete web, module.hot API le permite hacer esto.

  • React renderizador 16.9.0+ (por ejemplo, React DOM 16.9)
  • react-refresh/runtime punto de entrada
  • react-refresh/babel Complemento

Probablemente querrá trabajar en la parte de integración. Es decir, integrar react-refresh/runtime con el mecanismo de "reemplazo de módulo en caliente" de Webpack.

¿Cómo es la integración?

⚠️⚠️⚠️ PARA SER CLAROS, ESTA ES UNA GUÍA PARA LAS PERSONAS QUE QUIEREN IMPLEMENTAR LA INTEGRACIÓN POR SÍ MISMAS. ¡PROCEDA BAJO SU PROPIA PRECAUCIÓN!

Hay algunas cosas que desea hacer mínimamente:

  • Habilite HMR en su paquete (por ejemplo, paquete web)
  • Asegúrese de que React sea 16.9.0+
  • Agregue react-refresh/babel a sus complementos de Babel

En ese momento, su aplicación debería fallar. Debe contener llamadas a funciones $RefreshReg$ y $RefreshSig$ que no están definidas.

Luego, debe crear un nuevo punto de entrada JS que debe ejecutarse antes que cualquier código en su aplicación , incluido react-dom (!) Esto es importante; si se ejecuta después de react-dom , nada funcionará. Ese punto de entrada debería hacer algo como esto:

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

Esto debería solucionar los fallos. Pero aún así no hará nada porque estas implementaciones $RefreshReg$ y $RefreshSig$ son ningún error. Conectarlos es la parte principal del trabajo de integración que necesita hacer.

La forma de hacerlo depende de su agrupador. Supongo que con el paquete web podrías escribir un cargador que agregue código antes y después de que se ejecute cada módulo. O tal vez haya algún gancho para inyectar algo en la plantilla del módulo. Independientemente, lo que desea lograr es que cada módulo se vea así:

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

La idea aquí es que nuestro complemento de Babel emite llamadas a estas funciones, y luego nuestra integración anterior vincula esas llamadas al ID del módulo. Para que el tiempo de ejecución reciba cadenas como "path/to/Button.js Button" cuando se registra un componente. (O, en el caso del paquete web, los ID serían números). No olvide que tanto la transformación de Babel como esta envoltura solo deben ocurrir en el modo de desarrollo.

Como alternativa a envolver el código del módulo, tal vez haya alguna forma de agregar un try / finalmente como este en el lugar donde el empaquetado realmente inicializa la fábrica de módulos. Como lo hacemos aquí en Metro (RN bundler). Esto probablemente sería mejor porque no necesitaríamos sobrecargar cada módulo, o preocuparnos por introducir una sintaxis ilegal, por ejemplo, al envolver import con try / finally .

Una vez que conecte esto, tendrá un último problema. Su agrupador no sabe que está manejando las actualizaciones, por lo que probablemente vuelva a cargar la página de todos modos. Tienes que decirle que no lo haga. De nuevo, esto es específico del paquete, pero el enfoque que sugiero es verificar si todas las exportaciones son componentes de React y, en ese caso, "aceptar" la actualización. En webpack podría verse como algo:


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

¿Qué es isReactRefreshBoundary ? Es algo que enumera las exportaciones de manera superficial y determina si solo exporta componentes de React. Así es como decides si aceptar una actualización o no. No copié y pegué aquí, pero esta implementación podría ser un buen comienzo. (En ese código, Refresh refiere a react-refresh/runtime exportación).

También querrá registrar manualmente todas las exportaciones porque la transformación de Babel solo llamará $RefreshReg$ para funciones. Si no lo hace, no se detectarán las actualizaciones de las clases.

Finalmente, la función enqueueUpdate() sería algo compartido entre módulos que elimina los rebotes y realiza la actualización de React real.

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

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

En este punto, debería tener algo funcionando.

Matices

Hay algunas expectativas de experiencia de base que me importan y que se refieren a la marca "Actualización rápida". Debería poder recuperarse sin problemas de un error de sintaxis, un error de inicialización del módulo o un error de representación. No entraré en estos mecanismos en detalle, pero creo firmemente que no debería llamar a su experimento "Actualización rápida" hasta que maneje bien esos casos.

Desafortunadamente, no sé si el paquete web puede admitir todos esos, pero podemos pedir ayuda si llega a un estado de funcionamiento pero luego se queda atascado. Por ejemplo, he notado que la API accept() webpack hace que la recuperación de errores sea más difícil (debe aceptar una versión _previa_ del módulo después de un error), pero hay una manera de solucionarlo. Otra cosa a la que tendremos que volver es a "registrar" automáticamente todas las exportaciones, y no solo las encontradas por el complemento de Babel. Por ahora, ignoremos esto, pero si tiene algo que funcione, por ejemplo, webpack, puedo buscar pulirlo.

De manera similar, necesitaríamos integrarlo con una experiencia de "cuadro de error", similar a react-error-overlay que tenemos en la aplicación Create React. Eso tiene algunos matices, como que el cuadro de error debería desaparecer cuando corrige un error. Eso también requiere más trabajo que podemos hacer una vez que la base esté en su lugar.

¡Hazme saber si tienes alguna pregunta!

Los errores de sintaxis / errores de inicialización deberían ser "bastante fáciles" de manejar de alguna manera antes de decirle a React que inicie un renderizado, pero ¿cómo interactuarían los errores de renderizado con los límites de error?

Si ocurre un error de renderizado, activará el límite de error más cercano que se tomará instantáneamente en un estado de error, y no hay una forma genérica de decirle a los límites de error que sus hijos son mágicamente _posiblemente_ arreglados después de una actualización en vivo. ¿Cada componente actualizable debe obtener su propio límite de error de forma gratuita o el manejo de errores funciona de manera diferente en el reconciliador cuando se detecta el soporte en tiempo de ejecución en la inicialización?

todas las exportaciones son componentes de React, y en ese caso, "acepta" la actualización

¿Existe alguna forma de detectar dichos componentes? Por lo que tengo entendido, no. Excepto export.toString().indexOf('React')>0 , pero dejaría de funcionar con cualquier HOC aplicado.
Además, _auto aceptar_ al final del archivo no es propenso a errores: el nuevo identificador de aceptación no se establecería y la próxima actualización aparecería en el límite superior, por eso se creó require("react-hot-loader/root").hot .

En cualquier caso, parece ser que si uno arrojara todo el código específico de reacción de react-hot-loader , manteniendo intacta la API externa, eso sería suficiente y aplicable a todas las instalaciones existentes.

Usar react-refresh/babel 0.4.0 me está dando este error en una gran cantidad de archivos:

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)

Reduje ese archivo a la cosa más simple que lo causa:

import { useContext } from 'react'

export default () => useContext()

Si ocurre un error de renderizado, activará el límite de error más cercano que se tomará instantáneamente en un estado de error, y no hay una forma genérica de decirle a los límites de error que sus hijos están arreglados mágicamente después de una actualización en vivo.

El código de actualización rápida dentro de React recuerda qué límites fallaron actualmente. Siempre que se programe una actualización de Fast Refresh, siempre la volverá a montar.

Si no hay límites, pero una raíz falló en la actualización, Fast Refresh volverá a intentar representar esa raíz con su último elemento.

Si la raíz falló en el montaje, runtime.hasUnrecoverableErrors() se lo dirá. Entonces tienes que forzar una recarga. Podríamos manejar ese caso más tarde, todavía no tuve tiempo de arreglarlo.

El uso de react-refresh / babel 0.4.0 me está dando este error en una gran cantidad de archivos:

¿Presentar un nuevo problema por favor?

¿Existe alguna forma de detectar dichos componentes?

Me vinculé a mi implementación, que en sí misma usa Runtime.isLikelyAReactComponent() . No es perfecto, pero es lo suficientemente bueno.

el nuevo identificador de aceptación no se establecería y la próxima actualización aparecería en el límite superior

¿Puedes dar un ejemplo? No te estoy siguiendo. Independientemente, eso es algo específico del paquete. Hice que Metro hiciera lo que yo quería. Podemos pedirle a webpack que agregue algo si nos falta una API.

El objetivo aquí es volver a ejecutar la menor cantidad posible de módulos y garantizar la coherencia. No queremos a las actualizaciones de la burbuja a la raíz de la mayoría de las ediciones.

parece ser que si uno arrojara todo el código específico de reacción de react-hot-loader, manteniendo intacta la API externa

Quizás, aunque también me gustaría quitar el contenedor de nivel superior. También quiero una integración más estrecha con el cuadro de error. Tal vez todavía se pueda llamar react-hot-loader .

Por cierto, edité mi guía para incluir una pieza faltante que olvidé: la llamada performReactRefresh . Eso es lo que realmente programa las actualizaciones.

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

No me sentiría seguro con tal lógica. Incluso si casi siempre todas las CapitalizedFunctions son componentes de React, muchos módulos (los míos) también tienen otras exportaciones. Por ejemplo, _exports-for-tests_. Eso no es un problema, pero crea algo de _impredecibilidad_: el límite caliente podría crearse en cualquier punto ... o no crearse después de un cambio de línea.
¿Qué podría romper la prueba isLikelyComponentType ?

  • exportado mapStateToProps (para pruebas, no utilizado en un código de producción)
  • exportado hook (y eso está bien)
  • exportado Class que podría no ser una clase de reacción (no lo haría, pero debería)

Entonces, habría casos en los que se establecería un límite caliente, pero no lo haría, y habría casos en que se establecería un límite caliente, pero no se establecerá. Suena como una vieja recarga en caliente inestable que a los dos no nos gusta del todo :)

Hay un lugar donde la aplicación de límites calientes no sería tan impredecible, y sería bastante esperado: un límite de thing o domain , o un índice de directorio, es decir, un index.js reexportando una "API pública" de Component.js en el mismo directorio (no un afaik estilo Facebook).

En otras palabras, todo como lo hizo en Metro, pero con más limitaciones aplicadas. Todo lo demás, como la regla de linting para establecer dicho límite para cualquier componente cargado de forma diferida, podría usarse para hacer cumplir el comportamiento correcto.

Hablando de eso, ¿la actualización rápida en caliente manejaría Lazy? ¿Se espera que tenga un límite desde el otro lado del import ?

Lo intenté rápidamente para ver la magia en el navegador y es tan agradable :) Hice lo más simple posible, es decir, codificar todo el código de instrumentación, por lo que todavía no hay complementos de paquete web

Kapture 2019-09-07 at 23 09 04

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

Solo por curiosidad, pero para el paquete web, ¿no podrías tener un complemento de babel para completar el intento / finalmente? Solo quiero estar seguro de que no me estoy perdiendo nada antes de intentarlo.

El complemento de Babel no es específico del entorno. Me gustaría mantenerlo así. No sabe nada sobre módulos o mecanismo de propagación de actualizaciones. Éstos difieren según el paquete.

Por ejemplo, en Metro no hay ningún intento o transformación final. En su lugar, puse try / finalmente en el tiempo de ejecución del

Por supuesto, podría crear otro complemento de Babel para envolver. Pero eso no le compra nada por hacer eso a través del paquete web. Dado que es específico de paquete web de todos modos. Y puede ser confuso que accidentalmente pueda ejecutar ese complemento de Babel en otro entorno (no paquete web) donde no tendría sentido.

Puede hacerlo enganchándose en el gancho de cascada compilation.mainTemplate.hooks.require . La invocación anterior es el cuerpo predeterminado de la función __webpack_require__ , por lo que puede acceder al gancho para envolver el contenido en un bloque try/finally .

El problema es obtener una referencia a React dentro de __webpack_require__ . Es posible, pero puede requerir cierto grado de guardias de reentrada y recursividad.

Para obtener más detalles, consulte MainTemplate.js y web/JsonpMainTemplatePlugin.js en el código fuente del paquete web. JsonpMainTemplatePlugin sí solo aprovecha un montón de ganchos de MainTemplate.js por lo que probablemente sea la "carne" que necesita abordar.

Aquí hay un prototipo descabellado que pirateé y que hace efectivamente lo que Dan describió anteriormente. Está lamentablemente incompleto, pero demuestra una implementación de baja fidelidad en el paquete web: https://gist.github.com/maisano/441a4bc6b2954205803d68deac04a716

Algunas notas:

  • react-dom está codificado aquí, por lo que esto no funcionaría con renderizadores personalizados o subpaquetes (por ejemplo, react-dom/profiling ).
  • No he examinado demasiado a fondo cómo funcionan todas las variantes de plantillas de paquetes web, pero la forma en que envuelvo la ejecución del módulo es bastante hack. No estoy seguro de si este ejemplo funcionaría si, digamos, uno usa el destino de la biblioteca umd .

El problema es obtener una referencia a React dentro de __webpack_require__. Es posible, pero puede requerir cierto grado de guardias de reentrada y recursividad.

Supongo que te refieres a obtener una referencia a Refresh Runtime.

En Metro, resolví esto haciendo require.Refresh = RefreshRuntime lo antes posible. Luego, dentro de la implementación require , puedo leer una propiedad de la función require . No estará disponible de inmediato, pero no importará si lo configuramos con suficiente antelación.

@maisano Tuve que cambiar varias cosas y, en última instancia, no veo la función .accept llamada por webpack. Probé .accept(module.i, () => {}) y .accept(() => {}) (autoaceptable, excepto que esto no funciona en el paquete web). La propiedad hot está habilitada, veo que baja y se ejecuta a través de los módulos aceptados.

Así que terminé parcheando el paquete web para llamar a los módulos autoaceptables, y esa fue la solución final.

Aquí está el parche:

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

Sé que esto va en contra de su API, que quiere que sea un "errorCallback", recuerdo haber encontrado esto específicamente hace muchos años trabajando en nuestro HMR interno, y finalmente terminamos escribiendo nuestro propio paquete. Creo que el paquete admite la API de devolución de llamada "autoaceptable". Quizás valga la pena que abramos un problema en el paquete web y veamos si podemos fusionarlo. @sokra

Entonces ... pulí aún más el complemento basado en el trabajo de
https://github.com/pmmmwh/react-refresh-webpack-plugin
(Lo escribí en TypeScript porque no confío en mí mismo jugando con los componentes internos del paquete web cuando comencé, puedo convertir eso en JS / Flow simple)

Traté de eliminar la necesidad de un cargador para inyectar el código del módulo activo con el paquete web Dependency classes, pero aparentemente eso requerirá un nuevo análisis de todos los módulos (porque incluso con todas las funciones en línea, todavía necesitamos un referencia a react-refresh/runtime en algún lugar).

Otro problema es que no hay formas simples (afaik) de detectar archivos similares a JavaScript en el paquete web; por ejemplo, html-webpack-plugin usa el tipo javascript/auto , así que codifiqué lo que parece ser una máscara de archivo aceptable (JS / TS / Flow) para la inyección del cargador.

También agregué la recuperación de errores (al menos un error de sintaxis) según el comentario de @gaearon en este hilo de 5 años . Lo siguiente es recuperarse de los errores de reacción; sospecho que esto se puede hacer inyectando un límite de error global (algo así como AppWrapper de react-hot-loader ), que también abordará la interfaz del cuadro de error, pero no lo hizo todavía tengo tiempo para llegar a eso.

El problema planteado por @natew también se evita: se logra desacoplando la llamada enqueueUpdate y la llamada hot.accpet(errorHandler) .

@pmmmwh ¡ Qué momento! Acabo de crear un repositorio que se

No he llegado al manejo de errores en ningún caso, aunque el complemento aquí es un poco más sólido que el enfoque inicial que había tomado.

Lo siguiente es recuperarse de los errores de reacción; sospecho que esto se puede hacer inyectando un límite de error global (algo así como AppWrapper de react-hot-loader), que también abordará la interfaz del cuadro de error, pero no tuvo tiempo para llegar a eso todavía.

Eso ya debería funcionar fuera de la caja. No es necesario un límite de error personalizado o un ajuste aquí.

Lo siguiente es recuperarse de los errores de reacción; sospecho que esto se puede hacer inyectando un límite de error global (algo así como AppWrapper de react-hot-loader), que también abordará la interfaz del cuadro de error, pero no tuvo tiempo para llegar a eso todavía.

Eso ya debería funcionar fuera de la caja. No es necesario un límite de error personalizado o un ajuste aquí.

@gaearon Strange. Intenté lanzar errores en los componentes de la función de renderizado: si el error ocurre en return , HMR funciona, pero si ocurre en otro lugar, a veces no funcionará.

@pmmmwh ¡ Qué momento! Acabo de crear un repositorio que se

No he llegado al manejo de errores en ningún caso, aunque el complemento aquí es un poco más sólido que el enfoque inicial que había tomado.

@maisano ¿Qué debo decir? De hecho, comencé a trabajar en esto y me quedé atascado con el problema de la inyección de dependencia el fin de semana pasado ... Tu esencia me proporcionó la salida: tada:

si el error ocurre a cambio, HMR funciona, pero si ocurre en otro lugar, a veces no funcionará.

Necesitaría más detalles sobre lo que intentó exactamente y lo que quiere decir con "funciona" y "no funciona".

Hay muchas cosas que pueden salir mal si la integración del paquete de módulos no se implementa correctamente (que es el tema o este hilo). Esperaría que no haya nada en React que evite la recuperación de errores introducidos durante las ediciones. Puedes verificar que funciona en React Native 0.61 RC3.

@pmmmwh , @maisano la siguiente verificación omite módulos con componentes como exportaciones con nombre y no se establece ningún límite de actualización:

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

No sé por qué se necesita esto en Metro , pero en webpack getters simplemente devuelve las exportaciones con nombre y, por lo que puedo decir, no hay efectos secundarios. Por lo tanto, debería ser seguro eliminarlo.

Los componentes React.lazy @gaearon React.lazy (por ejemplo, rutas de división de código) no se vuelven a representar. ¿Eso es por diseño? Puedo ver que el límite de actualización está establecido, pero performReactRefresh() no parece hacer nada. Los cambios en los niños perezosos se actualizan bien, así que esto no es gran cosa, pero me pregunto si estamos haciendo algo mal ...

lazy es una pequeña máquina de estado: contiene una referencia al componente antiguo y esa referencia debe actualizarse.
Ahora imaginemos que lo era, y ahora se refiere a un objeto lazy ): tendrá que pensar en la fase loading nuevamente, y eso probablemente destruiría todo el árbol anidado.

Esperaría que un perezoso trabajara. Quizás algo se rompió. Necesito ver un caso de reproducción.

Dado que ha habido algunos prototipos, ¿deberíamos elegir uno y luego pasar esta discusión a sus problemas? E iterar allí.

Ahi esta:
https://github.com/maisano/react-refresh-plugin
y:
https://github.com/pmmmwh/react-refresh-webpack-plugin
He configurado una bifurcación del complemento de pmmmwh que funciona con [email protected] (también corrige las exportaciones con nombre):
https://github.com/WebHotelier/webpack-fast-refresh

¿Qué pasa con react-hot-loader ?

react-hot-loader respaldaron casi todas las características de fast refresh , pero hay pocos momentos históricos e integradores que no permiten respaldarlos todos y, sinceramente, no tiene sentido volver a implementarlos en términos "rhl" . Entonces, déjalo retirarse.

Necesitaría más detalles sobre lo que intentó exactamente y lo que quiere decir con "funciona" y "no funciona".

Hay muchas cosas que pueden salir mal si la integración del paquete de módulos no se implementa correctamente (que es el tema o este hilo). Esperaría que no haya nada en React que evite la recuperación de errores introducidos durante las ediciones. Puedes verificar que funciona en React Native 0.61 RC3.

Después de algunos ajustes, puedo verificar que funciona.

Sin embargo, parece que el complemento de babel no funcionaba para las clases. Lo he comprobado y esto parece suceder independientemente de la implementación, ya que todo el código inyectado y react-refresh/runtime funcionan correctamente. No estoy seguro de si esto es lo que se pretendía o si es específico de un paquete web, si es lo último, puedo intentar solucionarlo mañana. (También probé esto solo con el preajuste de metro, reproduzca la esencia aquí )

Estoy un poco seguro de que funciona para RN, pero en mi máquina actual no tengo un entorno a mano para probar en RN, así que si me pueden indicar la implementación del complemento de babel en metro o las transformaciones, sería realmente útil.

Dado que ha habido algunos prototipos, ¿deberíamos elegir uno y luego pasar esta discusión a sus problemas? E iterar allí.

¿Quizás podamos ir aquí ? Desde mi último comentario, he transferido todo el proyecto a JS simple y agregué algunas correcciones en la cola de actualización. No tengo que portar el complemento para webpack @ 5 , pero después de leer la bifurcación de @apostolos y la nueva lógica HMR en webpack @ next , las correcciones deberían ser sencillas.

Sí, el complemento de Babel no registra clases. La intención es que esto suceda a nivel del sistema de módulos. Se debe verificar que cada exportación sea "probable" un componente de React. (El tiempo de ejecución proporciona una función de verificación). Si es verdadero, registre la exportación, tal como lo haría el complemento de Babel. Dale una identificación como filename exports%export_name . Esto es lo que hace que las clases funcionen en Metro, ya que el complemento de Babel no las encontrará.

En otras palabras, dado que no podemos preservar el estado de la clase de todos modos, también podríamos confiar "ubicarlos" en el límite de exportaciones del módulo en lugar de intentar encontrarlos en línea en el código fuente con una transformación. Las exportaciones deberían actuar como un "recurso general" para los componentes que no encontramos con el complemento.

Mailchimp comenzó a usar una bifurcación del complemento que compartí por última vez. Se ha desarrollado un poco más y las personas que han optado por usarlo parecen estar bastante felices. Continuaremos repitiéndolo localmente. Planeo eliminar la bifurcación y publicar actualizaciones en sentido ascendente una vez que esté un poco más avanzado.

@gaearon ¿Pensamos en agregar un símbolo que podemos adjuntar a cosas que sabemos que son seguras para actualizar, pero no son componentes? Por ejemplo, tenemos un patrón como:

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

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

Donde create solo devuelve un objeto. Por ahora lo parcheé, pero podríamos agregar fácilmente un símbolo al objeto de exportación predeterminado que indica que este es un archivo seguro. No estoy seguro del mejor patrón exactamente.

Editar: ¡Me di cuenta de que esto puede ir a la implementación de actualización! Pensé que podría ser mejor en el tiempo de ejecución, pero quizás no. Con tantas implicaciones diferentes del cargador, puede ser mejor tener una forma estándar.

Avancemos 10 años. ¿Cómo se ve su base de código? ¿Permitir aquí, rechazar allí? ¿Cómo mantener estas banderas actualizadas? ¿Cómo razonar? Al igual que existen ubicaciones _seguras para actualizar_ e _inseguras_, debe conservarlas o no puede conciliarlas correctamente por alguna razón. ¿Qué razones en cada caso son razones válidas?

  • cuál symbols tendrá más - aproximadamente force allow recargar, o force disallow recargar
  • por qué es posible que desee reducir el límite de propagación de la actualización (es decir, aceptar la actualización en "este" límite del módulo) o desea aumentarlo (es decir, aceptar la actualización en "ese" límite del módulo)
  • ¿Qué pasaría si no se establecieran límites? ¿Es solo un problema de rendimiento o podría suceder algo más grave?

Hola amigos 👋 Estoy buscando echar una mano aquí. ¿Hemos acordado un solo repositorio / esfuerzo?

¿Es este repositorio compartido por @pmmmwh?
https://github.com/pmmmwh/react-refresh-webpack-plugin

¿O es este repositorio compartido por @maisano?
https://github.com/maisano/react-refresh-plugin

Parece que el de @pmmmwh se ha comprometido más recientemente. A menos que escuche lo contrario, voy a suponer que es en quien enfocarme.

La implementación en Parcel 2 ha comenzado aquí: https://github.com/parcel-bundler/parcel/pull/3654

¡Verano!

Para cualquiera que lo busque, una implementación de React Refresh para proyectos Rollup utilizando Nollup para el desarrollo: https://github.com/PepsRyuu/rollup-plugin-react-refresh

Probablemente no sea la implementación más limpia, pero funciona.

Para las soluciones de paquetes web, parece que no ha habido ningún lanzamiento oficial de los complementos anteriores, por lo que parece que la mejor solución HMR para reaccionar es la biblioteca de Dan aquí todavía: https://github.com/gaearon/react-hot-loader

¡Acabamos de enviar Parcel 2 alpha 3 con soporte para Fast Refresh de fábrica! No dudes en probarlo. 😍 https://twitter.com/devongovett/status/1197187388985860096?s=20

🥳 nota de desaprobación agregada a RHL 🥳

Una receta que he estado usando para probar esto en aplicaciones CRA usando el trabajo en progreso de @pmmmwh , react-app-rewired y 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

Editar ./package.json :

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

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

Disfrutando la experiencia hasta ahora. ¡Gracias por todo el trabajo de todos los involucrados!

¡Gracias @ drather19 !

Creé un repositorio basado en sus instrucciones, funciona:https://github.com/jihchi/react-app-rewired-react-refreshSi alguien desea probarlo y guardar algo de escritura, puede clonar el repositorio.


Consulte https://github.com/pmmmwh/react-refresh-webpack-plugin/tree/master/examples/cra-kitchen-sink

Y ... v0.1.0 para Webpack se acaba de enviar 🎉

@ drather19 @jihchi
Es posible que quieran cambiar a esa versión, que incluye una experiencia más unificada, así como muchas correcciones de errores en la implementación inicial.

@pmmmwh admite ts-loader + babel-loader ?

@pmmmwh admite ts-loader + babel-loader ?

Probé contra TS solo con Babel y funciona, por lo que si no funciona cuando usa ts + babel loaders, no dude en presentar un problema :)

@ drather19 Intenté clonar y ejecutar su repositorio, pero el servidor de desarrollo nunca se inicia.

Medio ambiente,
SO - OSX 10.14.6
Nodo: v12.13.0
Hilado -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 Intenté clonar y ejecutar su repositorio, pero el servidor de desarrollo nunca se inicia.

Medio ambiente,
SO - OSX 10.14.6
Nodo: v12.13.0
Hilado -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...

Esto se solucionó en la rama master del complemento y se lanzará mañana.

Logré hacer que el complemento webpack de @pmmmwh funcionara con una aplicación TypeScript React usando babel. Sin embargo, las compilaciones incrementales toman alrededor de 12 segundos en lugar de ~ 2 segundos con solo ts-loader. Voy a seguir jugando con esto para ver si me falta algo en el lado de la configuración de babel que hace que el rendimiento sea más cercano, pero por ahora es un lavado en comparación con ts-loader y actualizaciones completas.

@IronSean Por favor, ¿informarlo en el repositorio de ese complemento? Esto no suena normal.

Voy a seguir jugando con esto para ver si me falta algo en el lado de la configuración de babel que hace que el rendimiento sea más cercano, pero por ahora es un lavado en comparación con ts-loader y actualizaciones completas.

¿Te importaría publicar tu configuración allí? No podré resolver los problemas sin más contexto.

@pmmmwh Abrí este número para mover la discusión a su repositorio una vez que confirme que era su complemento el que marcaba la diferencia:
https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/20

¿Funcionará react-refresh ( React Fast Refresh ?) Con Preact, o es react-hot-loader la solución a largo plazo para Preact?

@Jumblemuddle que depende de Preact, pero deberían poder integrarse con Fast Refresh si así lo desean.

Para las personas de CRA que desean ejecutar Fast Refresh, he tenido más suerte con craco (frente a react-app-rewired + custom-cra) ahora a través de los siguientes 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 })],
        [],
      ),
    ],
  },
};

En particular, agregar webpackConfig.optimization.runtimeChunk = false; le permitirá agregar / eliminar ganchos y aún así una actualización rápida y elegante.

Disfrutando aún más de la experiencia mejorada. ¡Gracias a @ mmhand123 por el consejo a través de https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/25! (<- ¡resuelto!)

Basado en la sugerencia de @ drather19 , he publicado un complemento esetnik /ustom-cra-react-refresh .

Gracias a @ drather19 modifiqué ligeramente el código ahora puede funcionar en una configuración de monorepo de espacio de trabajo de hilo.

Primero, instale lo siguiente en los subpaquetes para los que desea habilitar la actualización rápida:

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

Luego agregue esto a 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 ¿Esperamos que Fast Refresh esté disponible en la CRA de forma predeterminada en algún momento?
si es así, ¿qué se requiere para eso?

Se requiere algo de trabajo para eso :-) que se está haciendo actualmente.

si se llamará a las funciones de HMR? por ejemplo componentDidMount.
Yo uso react-proxy y se llamará componentDidMount.
¿Y react 15.X puede usar Fast Refresh?

  • Se llamará componentDidMount . Además de unmount , las clases se recargarían por completo.
  • y es un buen momento para dejar de usar react-proxy . Bueno, probablemente deberías dejar de hacerlo hace unos años.
  • 15.X ? - absolutamente no. Fast Refresh __es una parte__ de react y, por lo tanto, existe solo en versiones modernas.

¿Entonces deberíamos usar Fast Refresh o react-hot-loader para reemplazar react-proxy?
¿Hay alguna forma de evitar que las funciones (componentDidMount) se ejecuten para HMR? - llamará al método para obtener nuevos datos.

¿Cómo debo usar react-hot-loader en JIT? - Compilación del navegador en tiempo real

  • ¿Entonces deberíamos usar Fast Refresh o react-hot-loader para reemplazar react-proxy?

    Pruebe fast refresh primero, luego RHL

  • ¿Hay alguna forma de evitar que las funciones (componentDidMount) se ejecuten para HMR? - llamará al método para obtener nuevos datos.

    (use ganchos ...), no confíe en el ciclo de vida del componente, obtenga los datos cuando sea necesario. Pruebe react-query , swr u otras soluciones.

En cuanto a la pregunta sobre cómo se debe usar el nuevo HMR, no creo que conozca las últimas ideas allí. Veo que @gaearon tiene un wip PR en el repositorio de CRA:

Para aclarar a los lectores, que las relaciones públicas están muy desactualizadas y ya no son relevantes.

Necesito escribir algo sobre cómo funciona Fast Refresh y cómo integrarlo. Todavía no he tenido tiempo.

A día de hoy, ese PR sigue abierto. Sería bueno si solo los RP relevantes que todavía tienen la posibilidad de fusionarse se mantuvieran abiertos para tener una mejor visión general. Si solo los mantiene como referencia, le recomendaría mover cosas a una rama, etiqueta o repositorio diferente.

Sigo recibiendo Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly. He seguido la documentación, pero parece que me he perdido algo.

Sigo recibiendo Error: [React Refresh] ¡Reemplazo de módulo activo (HMR) no está habilitado! React Refresh requiere que HMR funcione correctamente. He seguido la documentación, pero parece que me he perdido algo.

@silkfire Supongo que estás usando el complemento webpack. En caso afirmativo, presente su pregunta en el repositorio de complementos de paquete web: https://github.com/pmmmwh/react-refresh-webpack-plugin/.

A día de hoy, ese PR sigue abierto. Sería bueno si solo los RP relevantes que todavía tienen la posibilidad de fusionarse se mantuvieran abiertos para tener una mejor visión general. Si solo los mantiene como referencia, le recomendaría mover cosas a una rama, etiqueta o repositorio diferente.

Agradezco tu sugerencia, pero con miles de notificaciones sin leer, a veces me puede resultar difícil recordar volver a visitar los antiguos RP. Confío en que los mantenedores del repositorio Create React App hagan lo correcto y cierren si consideran que ya no es útil.

Voy a cerrar esto.

Tenemos https://github.com/pmmmwh/react-refresh-webpack-plugin/ como implementación de referencia para webpack.
Y https://github.com/facebook/react/issues/16604#issuecomment -528663101 explica cómo hacer una integración personalizada.

Sigo recibiendo Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly. He seguido la documentación, pero parece que me he perdido algo.

Parece que no ha habilitado el paquete web HMR. Para obtener más ayuda, presente un problema en el repositorio del complemento.

Como Hot Replacement ahora es parte de React, ¿debería tener un lugar separado en la documentación de React, señalando las bibliotecas adicionales que se usarán con paquetes y plataformas particulares, así como también explicando algunos errores que aún existen, como con css de actualización automática? módulos.

Información como esta no debe quedar oculta en problemas de github y publicaciones de blogs.

@theKashey está en React, pero la implementación de react-dom es solo experimental, por
Además, hay una implementación de actualización rápida que se incluirá con create-react-app, pero aún no se ha lanzado: pmmmwh / react-refresh-webpack-plugin # 7. Quizás sea en la próxima versión de react-scripts.

Así que probablemente el equipo de React actualmente no cree que sea correcto hablar de Fast Refresh para react-dom en esta fase experimental todavía.

está en React, pero la implementación de react-dom es solo experimental, por ejemplo.

Para ser claros, la implementación en react-dom sí misma es estable, al igual que en React Native. Es solo que las integraciones no son todas estables.

en caso de que tenga un lugar separado en la documentación de React, señalando las bibliotecas adicionales que se usarán con paquetes y plataformas particulares, así como también explicando algunos errores que aún existen, como con los módulos css de actualización automática.

Suena razonable. Me complacería tomar un PR para agregarlo a la sección Guías avanzadas, tal vez basado en una página RN similar .

@gaearon
Mi aplicación de reacción está bien con algunos cambios de componentes de estilo y aplicando correctamente esos cambios sin ningún problema.
Sin embargo, cuando cambio algún código en el reductor de Redux, toda la aplicación se actualiza y pierde todos los estados redux.
¿Necesito usar algunas otras bibliotecas como redux-persist para guardar el estado actual junto con react-fast-refresh ?

Hemos dado un giro completo y aquí vamos de nuevo 😅

Así es como funciona el HMR de bajo nivel y está fuera de la responsabilidad de actualización rápida. Consulte los documentos de redux o webpack

Hemos dado un giro completo y aquí vamos de nuevo 😅

Así es como funciona el HMR de bajo nivel y está fuera de la responsabilidad de actualización rápida. Consulte los documentos de redux o webpack

¿Vincularía la referencia del círculo completo?

@ jwchang0206 Asegúrate de tener un código como este en tu tienda.

¿Vincularía la referencia del círculo completo?

Se hicieron las mismas preguntas para React Hot Loader. Se dieron las mismas respuestas. Estamos en el inicio de un nuevo ciclo.

@ jwchang0206 Mira redux-redurs-injector , una pequeña biblioteca que escribí para abordar este problema.
Le permitirá soportar la recarga de reductores con recarga en caliente.
Asegúrese de seguir los principios de inmutabilidad de redux en sus reductores y funcionará sin problemas 💯
Y si está usando sagas, puede usar redux-sagas-injector .

@gaearon Estoy un poco confundido por el uso de window . ¿No me parece que sea realmente necesario porque la implementación se ha cambiado? ¿Cual es el punto de eso?

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
}

Tengo mi propio paquete personalizado y estoy en el proceso de implementación de esto, pero no puedo ver por qué sería una necesidad absoluta o cuál sería el punto ... inicialmente sospeché alguna optimización de uso / pérdida de memoria, pero son solo llamadas reenviadas al RefreshRuntime ...

@leidegre No puedo comentar sobre la decisión de establecer $ RefreshSig $ en el objeto de la ventana, pero el acoplamiento a un entorno de navegador me dio problemas al consumir Fast Refresh en React NativeScript. @pmmmwh vino al rescate adaptando su complemento Fast Refresh Webpack para superar el acoplamiento de Fast Refresh al navegador (los problemas encontrados y superados se discutieron en este hilo: https://github.com/pmmmwh/react-refresh-webpack-plugin/ cuestiones / 79). Me pregunto si el enfoque utilizado sería de alguna utilidad para usted en la integración de Fast Refresh de su paquete personalizado.

Mi empaquetadora es principalmente una envoltura alrededor del compilador de TypeScript. La implementación es principalmente esto, adaptado del visitante react-refresh/babel .

Esto es algo simple que funciona pero no es tan completo como el visitante 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
}

No estaba seguro de cómo usar createSignatureFunctionForTransform al principio, pero es solo una función de fábrica que crea una pequeña máquina de estado para cada componente. Entonces, lo llama una vez para cada función con la firma del gancho estático (que es solo un valor opaco, similar a un hash). Luego lo llama desde render para que termine su trabajo de configuración.

Cambia algo como esto:

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

Dentro de esto:

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

Tenga en cuenta que el visitante está incompleto. Solo se ocupa del caso de uso más básico.

Estoy un poco confundido por el uso de window . ¿No me parece que sea realmente necesario porque la implementación se ha cambiado? ¿Cual es el punto de eso?

@leidegre

Creo que la implementación en Metro no usa window sino el alcance real global .

No conozco la justificación original de esta implementación, pero ha sido útil desde mi experiencia: garantiza que la lógica de requerimiento real sea independiente de la lógica de actualización rápida (lo que significa que las transformaciones react-refresh/babel se pueden usar con prácticamente cualquier empaquetadora). Al igual que con el intercambio, también actúa como una protección para garantizar que los módulos que se supone que no deben ser procesados ​​por el tiempo de ejecución no serán procesados:

Considere un caso en el que se está usando @babel/runtime , que inyectará ayudantes como importaciones al paquete y solo desea node_modules código HMR que no sea cleanup antes de que el módulo user-land realmente termine la inicialización (porque son hijos importaciones).

¿Fue útil esta página
0 / 5 - 0 calificaciones