React: Como devemos configurar aplicativos para HMR agora que Fast Refresh substitui react-hot-loader?

Criado em 29 ago. 2019  ·  85Comentários  ·  Fonte: facebook/react

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

Eu:
Eu tenho este gancho:
require("react-reconciler")(hostConfig).injectIntoDevTools(opts);
Mas o HMR sempre funcionou completamente sem ele. Este agora é um novo requisito?

Dan:
Sim, é isso que o novo mecanismo usa. O novo mecanismo não precisa de "react-hot-loader", portanto, no momento da atualização, você deseja remover esse pacote. (É muito invasivo)

Não consigo ver nenhuma menção de HMR na documentação do Devtools, entretanto; agora que react-hot-loader se tornou obsoleto (e com ele, o método require("react-hot-loader/root").hot ), como devemos configurar aplicativos para HMR em:

  • React DOM apps
  • React Native apps
  • React custom renderer apps

Eu estaria particularmente interessado em um guia de migração especificamente para qualquer pessoa que já configurou o HMR por meio de react-hot-loader .

Além disso, para HMR, faz diferença se estamos usando Devtools autônomos ou Devtools de extensão de navegador?

Question

Comentários muito úteis

Ok, aqui vai.

O que é atualização rápida?

É uma reimplementação do "recarregamento a quente" com suporte total do React. Ele é originalmente enviado para React Native, mas a maior parte da implementação é independente de plataforma. O plano é usá-lo em todas as áreas - como um substituto para soluções puramente de usuário (como react-hot-loader ).

Posso usar a atualização rápida na Web?

Teoricamente, sim, esse é o plano. Praticamente, alguém precisa integrá-lo com bundlers comuns na web (por exemplo, Webpack, Parcel). Eu não comecei a fazer isso ainda. Talvez alguém queira pegá-lo. Este comentário é um guia aproximado de como você faria isso.

Em que consiste?

A atualização rápida depende de várias peças trabalhando juntas:

  • Mecanismo de "substituição de módulo a quente" no sistema de módulos.

    • Isso geralmente também é fornecido pelo empacotador.

    • Por exemplo, no webpack, module.hot API permite que você faça isso.

  • Renderizador React 16.9.0+ (por exemplo, React DOM 16.9)
  • react-refresh/runtime ponto de entrada
  • react-refresh/babel plugin do Babel

Você provavelmente vai querer trabalhar na parte de integração. Ou seja, integração de react-refresh/runtime com o mecanismo de "substituição de módulo ativo" do Webpack

Qual é a aparência da integração?

⚠️⚠️⚠️ PARA FICAR CLARO, ESTE É UM GUIA PARA PESSOAS QUE DESEJAM EXECUTAR A INTEGRAÇÃO MESMAS. PROSSEGUE POR SUA PRÓPRIA ATENÇÃO!

Existem algumas coisas que você deseja fazer no mínimo:

  • Habilite HMR em seu bundler (por exemplo, webpack)
  • Certifique-se de que React é 16.9.0+
  • Adicione react-refresh/babel aos seus plug-ins Babel

Nesse ponto, seu aplicativo deve travar. Ele deve conter chamadas para $RefreshReg$ e $RefreshSig$ funções que são indefinidas.

Em seguida, você precisa criar um novo ponto de entrada JS que deve ser executado antes de qualquer código em seu aplicativo , incluindo react-dom (!) Isso é importante; se for executado após react-dom , nada funcionará. Esse ponto de entrada deve fazer algo assim:

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

Isso deve corrigir as falhas. Mas ainda não fará nada porque essas implementações de $RefreshReg$ e $RefreshSig$ são noops. Conectá-los é a essência do trabalho de integração que você precisa fazer.

Como você faz isso depende do seu bundler. Suponho que com o webpack você possa escrever um carregador que adiciona algum código antes e depois da execução de cada módulo. Ou talvez haja algum gancho para injetar algo no modelo do módulo. Independentemente disso, o que você deseja alcançar é que cada módulo tenha a seguinte aparência:

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

A ideia aqui é que nosso plugin Babel emita chamadas para essas funções, e então nossa integração acima liga essas chamadas ao ID do módulo. Para que o tempo de execução receba strings como "path/to/Button.js Button" quando um componente está sendo registrado. (Ou, no caso do webpack, os IDs seriam números.) Não se esqueça de que a transformação de Babel e esse agrupamento devem ocorrer apenas no modo de desenvolvimento.

Como alternativa para empacotar o código do módulo, talvez haja alguma maneira de adicionar um try / finally como este ao redor do lugar onde o bundler realmente inicializa a fábrica de módulo. Como fazemos aqui no Metro (RN bundler). Isso provavelmente seria melhor porque não precisaríamos sobrecarregar todos os módulos ou nos preocupar com a introdução de sintaxe ilegal, por exemplo, ao envolver import em try / finally .

Depois de conectar isso, você terá um último problema. Seu bundler não sabe que você está lidando com as atualizações, então provavelmente recarrega a página de qualquer maneira. Você precisa dizer para não. Novamente, isso é específico do bundler, mas a abordagem que sugiro é verificar se todas as exportações são componentes React e, nesse caso, "aceitar" a atualização. No webpack, pode ser semelhante a 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();
}

O que é isReactRefreshBoundary ? É algo que enumera as exportações superficialmente e determina se exporta apenas componentes do React. É assim que você decide se aceita uma atualização ou não. Não copiei e colei aqui, mas esta implementação pode ser um bom começo. (Nesse código, Refresh refere-se a react-refresh/runtime exportação).

Você também vai querer registrar manualmente todas as exportações porque a transformação do Babel só chamará $RefreshReg$ para funções. Se você não fizer isso, as atualizações das classes não serão detectadas.

Finalmente, a função enqueueUpdate() seria algo compartilhado entre os módulos que eliminam e realizam a atualização real do React.

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

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

Nesse ponto, você deve ter algo funcionando.

Nuances

Existem algumas expectativas de experiência básicas que me preocupam em relação à marca "Fast Refresh". Ele deve ser capaz de se recuperar normalmente de um erro de sintaxe, um erro de inicialização do módulo ou um erro de renderização. Não vou entrar em detalhes sobre esses mecanismos, mas sinto fortemente que você não deve chamar seu experimento de "Atualização Rápida" até que ele trate bem esses casos.

Infelizmente, não sei se o webpack pode suportar tudo isso, mas podemos pedir ajuda se você ficar um pouco funcionando, mas depois travar. Por exemplo, notei que a API accept() do webpack torna a recuperação de erros mais difícil (você precisa aceitar uma versão _previous_ do módulo após um erro), mas há uma maneira de contornar isso. Outra coisa a que precisamos voltar é "registrar" automaticamente todas as exportações, e não apenas aquelas encontradas pelo plugin Babel. Por enquanto, vamos ignorar isso, mas se você tiver algo que funcione para, por exemplo, webpack, posso dar uma olhada nele.

Da mesma forma, precisaríamos integrá-lo com uma experiência de "caixa de erro", semelhante a react-error-overlay que temos em Criar aplicativo React. Isso tem algumas nuances, como a caixa de erro deve desaparecer quando você corrige um erro. Isso também exige mais trabalho que podemos fazer assim que a fundação estiver pronta.

Deixe-me saber se você tiver alguma dúvida!

Todos 85 comentários

Existe alguma confusão. O novo DevTools não permite o recarregamento a quente (ou nada tem a ver com o recarregamento). Em vez disso, as mudanças de recarregamento a quente nas quais Dan tem trabalhado usam o "gancho" que DevTools e React usam para se comunicar. Ele se adiciona no meio para que possa recarregar.

Eu editei o título para remover a menção de DevTools (uma vez que pode causar confusão).

Quanto à pergunta sobre como o novo HMR deve ser usado, acho que não sei o que há de mais recente nisso. Vejo que @gaearon tem uma limpeza de relações públicas no
https://github.com/facebook/create-react-app/pull/5958

Quanto à pergunta sobre como o novo HMR deve ser usado, acho que não sei o que há de mais recente nisso. Vejo que @gaearon tem uma limpeza de relações públicas no

Para esclarecer aos leitores, que RP está muito desatualizado e não é mais relevante.


Preciso escrever algo sobre como funciona o Fast Refresh e como integrá-lo. Ainda não tive tempo.

Ok, aqui vai.

O que é atualização rápida?

É uma reimplementação do "recarregamento a quente" com suporte total do React. Ele é originalmente enviado para React Native, mas a maior parte da implementação é independente de plataforma. O plano é usá-lo em todas as áreas - como um substituto para soluções puramente de usuário (como react-hot-loader ).

Posso usar a atualização rápida na Web?

Teoricamente, sim, esse é o plano. Praticamente, alguém precisa integrá-lo com bundlers comuns na web (por exemplo, Webpack, Parcel). Eu não comecei a fazer isso ainda. Talvez alguém queira pegá-lo. Este comentário é um guia aproximado de como você faria isso.

Em que consiste?

A atualização rápida depende de várias peças trabalhando juntas:

  • Mecanismo de "substituição de módulo a quente" no sistema de módulos.

    • Isso geralmente também é fornecido pelo empacotador.

    • Por exemplo, no webpack, module.hot API permite que você faça isso.

  • Renderizador React 16.9.0+ (por exemplo, React DOM 16.9)
  • react-refresh/runtime ponto de entrada
  • react-refresh/babel plugin do Babel

Você provavelmente vai querer trabalhar na parte de integração. Ou seja, integração de react-refresh/runtime com o mecanismo de "substituição de módulo ativo" do Webpack

Qual é a aparência da integração?

⚠️⚠️⚠️ PARA FICAR CLARO, ESTE É UM GUIA PARA PESSOAS QUE DESEJAM EXECUTAR A INTEGRAÇÃO MESMAS. PROSSEGUE POR SUA PRÓPRIA ATENÇÃO!

Existem algumas coisas que você deseja fazer no mínimo:

  • Habilite HMR em seu bundler (por exemplo, webpack)
  • Certifique-se de que React é 16.9.0+
  • Adicione react-refresh/babel aos seus plug-ins Babel

Nesse ponto, seu aplicativo deve travar. Ele deve conter chamadas para $RefreshReg$ e $RefreshSig$ funções que são indefinidas.

Em seguida, você precisa criar um novo ponto de entrada JS que deve ser executado antes de qualquer código em seu aplicativo , incluindo react-dom (!) Isso é importante; se for executado após react-dom , nada funcionará. Esse ponto de entrada deve fazer algo assim:

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

Isso deve corrigir as falhas. Mas ainda não fará nada porque essas implementações de $RefreshReg$ e $RefreshSig$ são noops. Conectá-los é a essência do trabalho de integração que você precisa fazer.

Como você faz isso depende do seu bundler. Suponho que com o webpack você possa escrever um carregador que adiciona algum código antes e depois da execução de cada módulo. Ou talvez haja algum gancho para injetar algo no modelo do módulo. Independentemente disso, o que você deseja alcançar é que cada módulo tenha a seguinte aparência:

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

A ideia aqui é que nosso plugin Babel emita chamadas para essas funções, e então nossa integração acima liga essas chamadas ao ID do módulo. Para que o tempo de execução receba strings como "path/to/Button.js Button" quando um componente está sendo registrado. (Ou, no caso do webpack, os IDs seriam números.) Não se esqueça de que a transformação de Babel e esse agrupamento devem ocorrer apenas no modo de desenvolvimento.

Como alternativa para empacotar o código do módulo, talvez haja alguma maneira de adicionar um try / finally como este ao redor do lugar onde o bundler realmente inicializa a fábrica de módulo. Como fazemos aqui no Metro (RN bundler). Isso provavelmente seria melhor porque não precisaríamos sobrecarregar todos os módulos ou nos preocupar com a introdução de sintaxe ilegal, por exemplo, ao envolver import em try / finally .

Depois de conectar isso, você terá um último problema. Seu bundler não sabe que você está lidando com as atualizações, então provavelmente recarrega a página de qualquer maneira. Você precisa dizer para não. Novamente, isso é específico do bundler, mas a abordagem que sugiro é verificar se todas as exportações são componentes React e, nesse caso, "aceitar" a atualização. No webpack, pode ser semelhante a 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();
}

O que é isReactRefreshBoundary ? É algo que enumera as exportações superficialmente e determina se exporta apenas componentes do React. É assim que você decide se aceita uma atualização ou não. Não copiei e colei aqui, mas esta implementação pode ser um bom começo. (Nesse código, Refresh refere-se a react-refresh/runtime exportação).

Você também vai querer registrar manualmente todas as exportações porque a transformação do Babel só chamará $RefreshReg$ para funções. Se você não fizer isso, as atualizações das classes não serão detectadas.

Finalmente, a função enqueueUpdate() seria algo compartilhado entre os módulos que eliminam e realizam a atualização real do React.

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

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

Nesse ponto, você deve ter algo funcionando.

Nuances

Existem algumas expectativas de experiência básicas que me preocupam em relação à marca "Fast Refresh". Ele deve ser capaz de se recuperar normalmente de um erro de sintaxe, um erro de inicialização do módulo ou um erro de renderização. Não vou entrar em detalhes sobre esses mecanismos, mas sinto fortemente que você não deve chamar seu experimento de "Atualização Rápida" até que ele trate bem esses casos.

Infelizmente, não sei se o webpack pode suportar tudo isso, mas podemos pedir ajuda se você ficar um pouco funcionando, mas depois travar. Por exemplo, notei que a API accept() do webpack torna a recuperação de erros mais difícil (você precisa aceitar uma versão _previous_ do módulo após um erro), mas há uma maneira de contornar isso. Outra coisa a que precisamos voltar é "registrar" automaticamente todas as exportações, e não apenas aquelas encontradas pelo plugin Babel. Por enquanto, vamos ignorar isso, mas se você tiver algo que funcione para, por exemplo, webpack, posso dar uma olhada nele.

Da mesma forma, precisaríamos integrá-lo com uma experiência de "caixa de erro", semelhante a react-error-overlay que temos em Criar aplicativo React. Isso tem algumas nuances, como a caixa de erro deve desaparecer quando você corrige um erro. Isso também exige mais trabalho que podemos fazer assim que a fundação estiver pronta.

Deixe-me saber se você tiver alguma dúvida!

Erros de sintaxe / erros de inicialização devem ser "fáceis o suficiente" de manipular de alguma forma antes de dizer ao React para iniciar uma renderização, mas como os erros de renderização interagiriam com os limites do erro?

Se ocorrer um erro de renderização, ele acionará o limite de erro mais próximo, que se autointegrará em um estado de erro, e não há uma maneira genérica de dizer aos limites de erro que seus filhos são magicamente _possivelmente_ corrigidos após uma atualização ao vivo. Cada componente atualizável obtém seu próprio limite de erro gratuitamente ou o tratamento de erros funciona de maneira diferente no reconciliador quando o suporte de tempo de execução é detectado na inicialização?

todas as exportações são componentes do React e, nesse caso, "aceite" a atualização

Existe alguma maneira de detectar esses componentes? Tanto quanto eu entendo - não. Exceto export.toString().indexOf('React')>0 , mas pararia de funcionar com qualquer HOC aplicado.
Além disso, _aprocura-se_ no final do arquivo não está sujeito a erros - o novo identificador de aceitação não seria estabelecido e a próxima atualização iria borbulhar para o limite superior, é por isso que require("react-hot-loader/root").hot foi criado.

Em qualquer caso - parece que se alguém jogasse todo o código específico da reação de react-hot-loader , mantendo a API externa intacta - isso seria o suficiente e aplicável a todas as instalações existentes.

Usar react-refresh/babel 0.4.0 está me causando este erro em um grande número de arquivos:

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)

Reduzi esse arquivo à coisa mais simples que o causa:

import { useContext } from 'react'

export default () => useContext()

Se ocorrer um erro de renderização, ele acionará o limite de erro mais próximo, que se autointegrará em um estado de erro, e não há uma maneira genérica de dizer aos limites de erro que seus filhos são possivelmente corrigidos magicamente após uma atualização ao vivo.

O código Fast Refresh dentro do React lembra quais limites estão falhando. Sempre que uma atualização Fast Refresh for agendada, ela sempre os remontará.

Se não houver limites, mas uma raiz falhou na atualização, o Fast Refresh tentará novamente renderizar essa raiz com seu último elemento.

Se a raiz falhou na montagem, runtime.hasUnrecoverableErrors() dirá isso a você. Então você tem que forçar uma recarga. Poderíamos cuidar desse caso mais tarde, não tive tempo de consertar ainda.

Usar react-refresh / babel 0.4.0 está me dando este erro em um grande número de arquivos:

Arquive um novo problema, por favor?

Existe alguma maneira de detectar esses componentes?

Vinculei à minha implementação, que por si só usa Runtime.isLikelyAReactComponent() . Não é perfeito, mas é bom o suficiente.

o novo identificador de aceitação não seria estabelecido, e a próxima atualização iria borbulhar para o limite superior

Você pode dar um exemplo? Eu não estou entendendo. Independentemente disso, isso é algo específico do empacotador. Fiz o Metro fazer o que eu queria. Podemos pedir ao webpack para adicionar algo se estiver faltando uma API.

O objetivo aqui é reexecutar o mínimo de módulos possível, garantindo a consistência. Nós não queremos a atualizações de bolha para a raiz para a maioria das edições.

parece que se alguém jogasse todo o código específico do react-hot-loader, mantendo a API externa intacta

Talvez, embora eu também queira remover o contêiner de nível superior. Também quero uma integração mais estreita com a caixa de erro. Talvez isso ainda possa ser chamado de react-hot-loader .

A propósito, editei meu guia para incluir uma peça que faltava que esqueci - a chamada performReactRefresh . É isso que realmente programa as atualizações.

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

Eu não me sentiria seguro com tal lógica. Mesmo que todos CapitalizedFunctions sejam componentes React quase sempre - muitos módulos (meus) também têm outras exportações. Por exemplo _exports-for-tests_. Isso não é um problema, mas cria alguma _unprevisibilidade_ - limite ativo pode ser criado em qualquer ponto ... ou não criado após uma mudança de linha.
O que poderia quebrar o teste de isLikelyComponentType :

  • exportou mapStateToProps (para testes, não usado em um código de produção)
  • exportou hook (e está tudo bem)
  • exportou Class que pode não ser uma classe de reação (não seria, mas deveria)

Portanto - haveria casos em que o limite quente seria estabelecido, mas não seria, e haveria casos em que o limite quente seria estabelecido, mas não será. Soa como o velho e bom recarregamento instável a quente que nós dois não gostamos :)

Há um lugar onde a aplicação de limite quente não seria tão imprevisível e seria bastante esperado - um limite thing ou domain , ou um índice de diretório, ou seja, uma reexportação index.js uma "API pública" de Component.js no mesmo diretório (não um afaik no estilo do Facebook).

Em outras palavras - tudo como você fez no Metro, mas com mais limitações aplicadas. Todo o resto, como a regra de linting para estabelecer esse limite para qualquer componente carregado lentamente, pode ser usado para impor o comportamento correto.

Falando nisso - uma atualização rápida a quente resolveria o Lazy? É esperado que haja um limite do outro lado de import ?

Dei uma rápida tentativa para ver a mágica no navegador e é tão bom :) Fiz a coisa mais simples possível, ou seja, codificar todo o código de instrumentação, então nenhum plug-in webpack ainda

Kapture 2019-09-07 at 23 09 04

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

Só por curiosidade, mas para o webpack, você não poderia apenas ter um plugin babel para embrulhar o try / finally? Só quero ter certeza de que não estou perdendo nada antes de tentar.

O plugin Babel não é específico do ambiente. Eu gostaria de manter assim. Ele não sabe nada sobre módulos ou mecanismo de propagação de atualizações. Eles diferem dependendo do bundler.

Por exemplo, no Metro não há nenhuma transformação try / finally wrapping. Em vez disso, coloco try / finally no próprio tempo de execução do

Você pode, é claro, criar outro plugin do Babel para embrulhar. Mas isso não compra nada além de fazer isso via webpack. Uma vez que é específico do webpack de qualquer maneira. E pode ser confuso que você possa acidentalmente executar esse plugin do Babel em outro ambiente (não webpack) onde não faria sentido.

Você pode, conectando-se ao gancho cachoeira compilation.mainTemplate.hooks.require . A chamada anterior dele é o corpo padrão da função __webpack_require__ , então você pode tocar no gancho para embrulhar o conteúdo em um bloco try/finally .

O problema é obter uma referência para React dentro de __webpack_require__ . É possível, mas pode exigir algum grau de reentrada e guardas de recursão.

Para obter mais detalhes, verifique MainTemplate.js e web/JsonpMainTemplatePlugin.js no código-fonte do webpack. JsonpMainTemplatePlugin si apenas se encaixa em um monte de ganchos de MainTemplate.js então essa é provavelmente a "carne" que você precisa enfrentar.

Aqui está um protótipo estúpido que eu cortei e que faz o que Dan descreveu acima. É lamentavelmente incompleto, mas prova uma implementação lo-fi no webpack: https://gist.github.com/maisano/441a4bc6b2954205803d68deac04a716

Algumas notas:

  • react-dom está codificado aqui, então isso não funcionaria com renderizadores personalizados ou subpacotes (por exemplo, react-dom/profiling ).
  • Eu não olhei muito profundamente em como todas as variantes de template do webpack funcionam, mas a forma como envolvi a execução do módulo é bastante hackeada. Não tenho certeza se este exemplo funcionaria se, digamos, alguém usar o destino de biblioteca umd .

O problema é obter uma referência para React dentro do __webpack_require__. É possível, mas pode exigir algum grau de reentrada e guardas de recursão.

Suponho que você queira obter uma referência para Refresh Runtime.

No Metro, resolvi isso fazendo require.Refresh = RefreshRuntime mais cedo possível. Então, dentro da implementação require , posso ler uma propriedade da própria função require . Não estará disponível imediatamente, mas não importa se o definirmos com antecedência.

@maisano Tive que mudar várias coisas, e ultimamente não estou vendo a função .accept chamada pelo webpack. Eu tentei .accept(module.i, () => {}) e .accept(() => {}) (auto-aceitação, exceto que isso não funciona no webpack). A propriedade hot está habilitada, eu vejo que ela desce e passa pelos módulos aceitos.

Então, acabei corrigindo o webpack para chamar módulos de auto-aceitação, e essa foi a correção final.

Aqui está o 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",

Sei que isso vai contra a API deles, que quer que seja um "errorCallback", lembro-me de ter encontrado isso especificamente há muitos anos, trabalhando em nosso HMR interno e, no final das contas, acabamos escrevendo nosso próprio bundler. Eu acredito que parcel oferece suporte à API de retorno de chamada "auto-aceita". Talvez valha a pena abrir um problema no webpack e ver se podemos mesclá-lo? @sokra

Então ... dei um polimento adicional no plugin baseado no trabalho de @maisano :
https://github.com/pmmmwh/react-refresh-webpack-plugin
(Eu escrevi em TypeScript porque não confio em mim mesmo mexendo com componentes internos do webpack quando comecei, posso converter isso para JS / Flow simples)

Tentei remover a necessidade de um carregador para injetar o código do módulo quente com classes do webpack Dependency , mas aparentemente isso exigirá uma nova análise de todos os módulos (porque mesmo com todas as funções embutidas, ainda precisamos de um referência a react-refresh/runtime em algum lugar).

Outro problema é que não há maneiras simples (afaik) de detectar arquivos semelhantes a JavaScript em webpack - por exemplo, html-webpack-plugin usa o tipo javascript/auto também, então codifiquei o que parece ser uma máscara de arquivo aceitável (JS / TS / Flow) para injeção de carregador.

Eu também adicionei a recuperação de erro (pelo menos erro de sintaxe) com base no comentário de @gaearon neste 5 anos . Em seguida, está se recuperando de erros de reação - suspeito que isso pode ser feito injetando um limite de erro global (tipo AppWrapper de react-hot-loader ), que também resolverá a interface da caixa de erro, mas não ainda tenho tempo para fazer isso.

O problema levantado por @natew também é evitado - conseguido enqueueUpdate chamada hot.accpet(errorHandler) .

@pmmmwh Que momento! Acabei de criar um repo que se baseou / ajustou um pouco do trabalho que compartilhei na essência.

Não cheguei ao tratamento de erros em nenhum caso, embora o plugin aqui seja um pouco mais sólido do que a abordagem inicial que fiz.

Em seguida, está se recuperando de erros de reação - suspeito que isso pode ser feito injetando um limite de erro global (meio como AppWrapper de react-hot-loader), que também resolverá a interface de caixa de erro, mas não teve tempo de chegar a isso ainda.

Isso já deve funcionar fora da caixa. Não há necessidade de um limite de erro personalizado ou agrupamento aqui.

Em seguida, está se recuperando de erros de reação - suspeito que isso pode ser feito injetando um limite de erro global (meio como AppWrapper de react-hot-loader), que também resolverá a interface de caixa de erro, mas não teve tempo de chegar a isso ainda.

Isso já deve funcionar fora da caixa. Não há necessidade de um limite de erro personalizado ou agrupamento aqui.

@gaearon Strange. Eu tentei lançar erros na renderização de componentes da função - se o erro ocorrer em return , o HMR funciona, mas se ocorrer em outro lugar, às vezes não funcionará.

@pmmmwh Que momento! Acabei de criar um repo que se baseou / ajustou um pouco do trabalho que compartilhei na essência.

Não cheguei ao tratamento de erros em nenhum caso, embora o plugin aqui seja um pouco mais sólido do que a abordagem inicial que fiz.

@maisano O que devo dizer? Na verdade, comecei a trabalhar nisso e fiquei preso ao problema da injeção de dependência no fim de semana passado ... Sua essência me forneceu a saída: tada:

se o erro ocorrer de volta, o HMR funciona, mas se ocorrer em outro lugar, às vezes não funciona.

Eu precisaria de mais detalhes sobre o que você tentou exatamente e o que entende por “funciona” e “não funciona”.

Há muitas coisas que podem dar errado se a integração do bundler de módulo não for implementada corretamente (que é o tópico ou este thread). Eu esperaria que não haja nada no próprio React que impeça a recuperação de erros introduzidos durante as edições. Você pode verificar se ele funciona no React Native 0.61 RC3.

@pmmmwh , @maisano a seguinte verificação ignora módulos com componentes como exportações nomeadas e nenhum limite de atualização é estabelecido:

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

Não sei por que isso é necessário em Metro , mas em webpack getters apenas retornam as exportações nomeadas e, pelo que posso dizer, não há efeitos colaterais. Portanto, deve ser seguro remover.

@gaearon React.lazy componentes (por exemplo, rotas de divisão de código) não estão sendo renderizados novamente. Isso é intencional? Posso ver que o limite de atualização foi estabelecido, mas performReactRefresh() não parece fazer nada. Mudanças para crianças preguiçosas atualizam bem, então isso não é grande coisa, mas eu me pergunto se estamos fazendo algo errado ...

lazy é uma pequena máquina de estado - ela contém uma referência ao componente antigo, e essa referência deve ser atualizada.
Agora vamos imaginar que foi, e agora está se referindo a um novo objeto lazy ) - ele terá que passar pela fase loading novamente, e isso provavelmente destruiria todas as árvores aninhadas.

Eu esperaria preguiça de trabalhar. Talvez algo tenha quebrado. Eu preciso ver uma caixa de reprodução.

Uma vez que existem alguns protótipos, devemos escolher um e então mover esta discussão para seus problemas? E itere lá.

Há sim:
https://github.com/maisano/react-refresh-plugin
e:
https://github.com/pmmmwh/react-refresh-webpack-plugin
Eu configurei um fork do plugin do pmmmwh que funciona com [email protected] (também corrige as exportações nomeadas):
https://github.com/WebHotelier/webpack-fast-refresh

E quanto a react-hot-loader ?

react-hot-loader backport de quase todos os recursos de fast refresh , mas há poucos momentos históricos e integracionais que não permitem o backport de todos e, honestamente, não faz sentido reimplementá-los em termos "rhl" . Então - deixe-o se aposentar.

Eu precisaria de mais detalhes sobre o que você tentou exatamente e o que entende por “funciona” e “não funciona”.

Há muitas coisas que podem dar errado se a integração do bundler de módulo não for implementada corretamente (que é o tópico ou este thread). Eu esperaria que não haja nada no próprio React que impeça a recuperação de erros introduzidos durante as edições. Você pode verificar se ele funciona no React Native 0.61 RC3.

Depois de alguns ajustes, posso verificar se funciona.

No entanto - parece que o plugin babel não estava funcionando para as classes. Eu verifiquei e isso parece acontecer independentemente da implementação, já que todo o código injetado e react-refresh/runtime funcionam corretamente. Não tenho certeza se isso é intencional ou se é específico do webpack. Se for o último, posso tentar consertar amanhã. (Eu também testei isso apenas com a predefinição metro, reproduza a essência aqui )

Tenho certeza de que funciona para RN, mas na minha máquina atual não tenho um ambiente acessível para testar no RN, então se você puder me indicar a implementação do plugin babel no metro ou as transformações que seriam realmente úteis.

Uma vez que existem alguns protótipos, devemos escolher um e então mover esta discussão para seus problemas? E itere lá.

Talvez possamos ir aqui ? Desde meu último comentário, transferi todo o projeto para JS simples e adicionei algumas correções no enfileiramento de atualizações. Eu não tenho que portar o plugin para webpack @ 5 , mas depois de ler o fork por @apostolos e a nova lógica HMR no webpack @ , as correções devem ser diretas.

Sim, o plugin Babel não registra classes. A intenção é que isso aconteça no nível do sistema do módulo. Cada exportação deve ser verificada para ser "provável" um componente React. (Uma função de verificação é fornecida pelo tempo de execução.) Se verdadeiro, registre a exportação, assim como o plug-in Babel faria. Dê a ele um ID como filename exports%export_name . É isso que faz as classes funcionarem no Metro, já que o plugin do Babel não as encontrará.

Em outras palavras, uma vez que não podemos preservar o estado da classe de qualquer maneira, podemos também confiar a "localização" deles no limite de exportações do módulo em vez de tentar encontrá-los embutidos no código-fonte com uma transformação. As exportações devem funcionar como um “pega-tudo” para componentes que não encontramos com o plugin.

Mailchimp começou a usar um fork do plugin que compartilhei por último. Foi desenvolvido um pouco mais e as pessoas que optaram por usá-lo parecem estar muito felizes. Vamos continuar a iterar localmente. Pretendo remover o fork e publicar as atualizações no upstream assim que estiver um pouco mais adiante.

@gaearon Reflexões sobre como adicionar um símbolo que podemos anexar a coisas que sabemos que são seguras para atualização, mas não são componentes? Por exemplo, temos um padrão como:

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

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

Onde create apenas retorna um objeto. Eu apliquei um patch nele por enquanto, mas poderíamos facilmente adicionar um símbolo ao objeto de exportação padrão que indica que este é um arquivo seguro. Não tenho certeza do melhor padrão exatamente.

Edit: Eu percebi que isso pode ir para a implementação de atualização! Achei que poderia ser melhor no tempo de execução, mas talvez não. Com tantos impls diferentes do carregador, pode ser melhor ter um caminho padrão.

Vamos avançar 10 anos. Qual é a aparência de sua base de código? Permitir aqui, não permitir lá? Como manter essas sinalizações atualizadas? Como raciocinar? Como há locais _seguros para atualizar_ e _inseguros_, você deve preservar ou não pode reconciliar corretamente por algum motivo. Quais razões em cada caso são razões válidas?

  • qual symbols você terá mais - cerca de force allow reload ou force disallow reload
  • porque você pode querer diminuir o limite de propagação da atualização (ou seja, aceitar a atualização "neste" limite do módulo), ou deseja aumentá-lo (ou seja, aceitar a atualização "naquele" limite do módulo)
  • o que aconteceria se nenhum limite fosse definido? É apenas um problema de desempenho ou algo mais grave pode acontecer?

Oi pessoal 👋 Estou procurando dar uma mão aqui. Nós concordamos com um único repo / esforço?

É este repositório compartilhado por @pmmmwh?
https://github.com/pmmmwh/react-refresh-webpack-plugin

Ou é esse repo compartilhado por @maisano?
https://github.com/maisano/react-refresh-plugin

Parece que o de @pmmmwh foi comprometido mais recentemente. A menos que eu ouça o contrário, presumo que seja esse o foco.

A implementação na Parcela 2 começou aqui: https://github.com/parcel-bundler/parcel/pull/3654

Verão!

Para quem procura por isso, uma implementação de React Refresh for Rollup projetos usando Nollup para desenvolvimento: https://github.com/PepsRyuu/rollup-plugin-react-refresh

Provavelmente não é a implementação mais limpa, mas funciona.

Para soluções webpack, parece que não houve nenhum lançamento oficial dos plug-ins acima, então parece que a melhor solução HMR para react é a biblioteca de Dan ainda aqui: https://github.com/gaearon/react-hot-loader

Acabamos de enviar o pacote 2 alfa 3 com suporte para atualização rápida fora da caixa! Sinta-se à vontade para experimentar. 😍 https://twitter.com/devongovett/status/1197187388985860096?s=20

🥳 adicionada nota de descontinuação ao RHL 🥳

Uma receita que tenho usado para experimentar em aplicativos CRA usando o trabalho em andamento de @pmmmwh , react-app-rewired e 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"
  },

Adicionar ./config-overrides.js arquivo:

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

Aproveitando a experiência até agora. Obrigado por todo o trabalho de todos os envolvidos!

Obrigado @ drather19 !

Eu criei um repositório baseado em suas instruções, ele funciona:https://github.com/jihchi/react-app-rewired-react-refreshSe alguém quiser experimentar e economizar alguma digitação, sinta-se à vontade para clonar o repositório.


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

E ... v0.1.0 para Webpack acaba de ser enviado 🎉

@ drather19 @jihchi
Vocês podem querer mudar para essa versão - ela inclui uma experiência mais unificada, bem como uma série de correções de bugs na implementação inicial.

@pmmmwh suporta ts-loader + babel-loader ?

@pmmmwh suporta ts-loader + babel-loader ?

Eu testei somente no TS com Babel e funciona, então se não funcionar quando você usar os carregadores ts + babel, sinta-se à vontade para registrar um problema :)

@ drather19 Tentei clonar e executar seu repo, mas o servidor de desenvolvimento nunca inicializa.

Meio Ambiente,
OS - OSX 10.14.6
Nó - v12.13.0
Yarn -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 Tentei clonar e executar seu repo, mas o servidor de desenvolvimento nunca inicializa.

Meio Ambiente,
OS - OSX 10.14.6
Nó - v12.13.0
Yarn -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...

Isso foi corrigido no branch master do plugin e será lançado amanhã.

Eu consegui @pmmmwh 's Webpack plug-in trabalhar com um texto datilografado Reagir aplicativo usando babel. No entanto, as compilações incrementais levam cerca de 12 segundos em vez de cerca de 2 segundos com apenas ts-loader. Vou continuar brincando com isso para ver se estou faltando alguma coisa no lado da configuração do babel que torna a performance mais próxima, mas por agora é uma lavagem em comparação com ts-loader e atualizações completas.

@IronSean Por favor, relate no

Vou continuar brincando com isso para ver se estou faltando alguma coisa no lado da configuração do babel que torna a performance mais próxima, mas por agora é uma lavagem em comparação com ts-loader e atualizações completas.

Importa-se de postar sua configuração / instalação lá? Não vou conseguir descobrir os problemas sem mais contexto.

@pmmmwh Abri este problema para mover a discussão para seu repo assim que confirmei que era realmente seu plug-in fazendo a diferença:
https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/20

react-refresh ( React Fast Refresh ?) Funcionará com Preact, ou react-hot-loader a solução de longo prazo para Preact?

@Jumblemuddle que depende do Preact, mas devem ser capazes de se integrar ao Fast Refresh se quiserem.

Para o pessoal do CRA que deseja executar o Fast Refresh, tive mais sorte com o craco (vs. react-app-rewired + customize-cra) agora por meio do seguinte 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 })],
        [],
      ),
    ],
  },
};

Em particular, adicionar webpackConfig.optimization.runtimeChunk = false; permitirá que você adicione / remova ganchos e ainda atualize rapidamente.

Aproveitando a experiência aprimorada ainda mais agora. Obrigado a @ mmhand123 pela dica via https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/25! (<- resolvido!)

Com base na sugestão de @ drather19 , publiquei um plugin customize-cra para torná-lo mais fácil. Veja esetnik / customize-cra-react-refresh .

Graças a @ drather19 , modifiquei ligeiramente o código, agora ele pode funcionar em uma configuração monorepo do espaço de trabalho do yarn.

Primeiro, instale o seguinte nos subpacotes que deseja ativar a atualização rápida:

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

Em seguida, adicione isso 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 a Atualização Rápida esteja disponível no CRA por padrão em algum momento?
em caso afirmativo, o que é necessário para isso?

É necessário algum trabalho para isso :-) que está sendo feito atualmente.

se usar funções HMR serão chamadas? por exemplo, componentDidMount.
Eu uso o react-proxy e o componentDidMount será chamado.
E o react 15.X pode usar Fast Refresh?

  • componentDidMount será chamado. Assim como unmount - as classes seriam totalmente recarregadas.
  • e é um bom momento para parar de usar react-proxy . Bem, você deveria ter parado há alguns anos, provavelmente.
  • 15.X ? - absolutamente não. A atualização rápida __é uma parte__ da reação e, portanto, existe apenas em versões modernas.

então devemos usar Fast Refresh ou react-hot-loader para substituir o react-proxy?
Existe uma maneira de evitar que funções (componentDidMount) sejam executadas para HMR? - chamará o método para obter novos dados.

Como devo usar o react-hot-loader no JIT? - Compilação do navegador em tempo real

  • então devemos usar Fast Refresh ou react-hot-loader para substituir o react-proxy?

    Experimente fast refresh primeiro, depois RHL

  • Existe uma maneira de evitar que funções (componentDidMount) sejam executadas para HMR? - chamará o método para obter novos dados.

    (use ganchos ...), não dependa do componente lifeCycle, busque os dados quando necessário. Experimente react-query , swr ou outras soluções.

Quanto à pergunta sobre como o novo HMR deve ser usado, acho que não sei o que há de mais recente nisso. Vejo que @gaearon tem uma limpeza de relações públicas no

Para esclarecer aos leitores, que RP está muito desatualizado e não é mais relevante.

Preciso escrever algo sobre como funciona o Fast Refresh e como integrá-lo. Ainda não tive tempo.

Até hoje, esse PR ainda está aberto. Seria bom se apenas os PRs relevantes que ainda têm uma chance de serem mesclados fossem mantidos abertos para ter uma visão geral melhor. Se você está apenas mantendo-os como uma referência, eu recomendo mover as coisas para um branch, tag ou repositório diferente.

Continuo recebendo Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly. Segui a documentação, mas parece que esqueci algo?

Continuo recebendo o erro: [React Refresh] A substituição de módulo quente (HMR) não está ativada! React Refresh requer que o HMR funcione corretamente. Segui a documentação, mas parece que perdi algo?

@silkfire Presumo que você esteja usando o plugin webpack. Em caso afirmativo, envie sua pergunta no repositório do plug-in webpack: https://github.com/pmmmwh/react-refresh-webpack-plugin/.

Até hoje, esse PR ainda está aberto. Seria bom se apenas os PRs relevantes que ainda têm uma chance de serem mesclados fossem mantidos abertos para ter uma visão geral melhor. Se você está apenas mantendo-os como uma referência, eu recomendo mover as coisas para um branch, tag ou repositório diferente.

Agradeço sua sugestão, mas com milhares de notificações não lidas, às vezes pode ser difícil para mim lembrar de revisitar PRs antigos. Confio que os mantenedores do repositório do Create React App farão a coisa certa e fecharão se não considerarem mais útil.

Eu vou fechar isso.

Temos https://github.com/pmmmwh/react-refresh-webpack-plugin/ como uma implementação de referência para webpack.
E https://github.com/facebook/react/issues/16604#issuecomment -528663101 explica como fazer uma integração personalizada.

Continuo recebendo Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly. Segui a documentação, mas parece que esqueci algo?

Parece que você não habilitou o HMR do webpack. Para obter mais ajuda, registre um problema no repositório do plug-in.

Como o Hot Replacement agora faz parte do React - deve ter um lugar separado na documentação do React, apontando para as bibliotecas adicionais a serem usadas com bundlers e plataformas particulares, bem como explicando alguns truques ainda existentes, como css de autoatualização módulos.

Informações como essa não devem ser enterradas em questões de github e postagens de blog.

@theKashey está no React, mas a implementação do começar .
Além disso, há uma implementação de atualização rápida que será fornecida com o create-react-app, mas ainda não foi lançada: pmmmwh / react-refresh-webpack-plugin # 7. Talvez seja na próxima versão dos scripts de reação.

Portanto, provavelmente a equipe React atualmente não acha certo falar sobre Fast Refresh para react-dom nesta fase experimental ainda.

está no React, mas a implementação do react-dom é apenas experimental, para começar.

Para ser claro, a implementação em react-dom si é estável, assim como no React Native. Acontece que nem todas as integrações são estáveis.

deve ter um lugar separado na documentação do React, apontando para as bibliotecas adicionais a serem usadas com bundlers e plataformas particulares, bem como explicando algumas dicas ainda existentes, como módulos css de atualização automática.

Isso parece razoável. Eu ficaria feliz em receber um PR adicionando-o à seção Guias Avançados, talvez com base em uma página RN semelhante .

@gaearon
Meu aplicativo react está bem com algumas alterações de componentes de estilo e aplicando corretamente essas alterações sem problemas.
No entanto, quando eu altero algum código em um redutor do Redux, todo o aplicativo é atualizado e perde todos os estados de redux.
Preciso usar alguma outra biblioteca como redux-persist para salvar o estado atual junto com react-fast-refresh ?

Fizemos um círculo completo e lá vamos nós de novo 😅

É assim que o HMR de baixo nível funciona e está fora da responsabilidade de atualização rápida. Consulte a documentação redux ou webpack

Fizemos um círculo completo e lá vamos nós de novo 😅

É assim que o HMR de baixo nível funciona e está fora da responsabilidade de atualização rápida. Consulte a documentação redux ou webpack

Você vincularia a referência do círculo completo?

@ jwchang0206 Certifique-se de ter um código como este em sua loja.

Você vincularia a referência do círculo completo?

As mesmas perguntas foram feitas para React Hot Loader. As mesmas respostas foram dadas. Estamos no início de um novo ciclo.

@ jwchang0206 Veja redux- reducers -injector , uma pequena biblioteca que escrevi para resolver esse problema.
Isso permitirá que você ofereça suporte a redutores de recarga com recarga a quente.
Certifique-se de seguir os princípios redux de imutabilidade em seus redutores e funcionará bem 💯
E se você estiver usando sagas, você pode usar redux-sagas-injector .

@gaearon Estou um pouco confuso com o uso de window . Não me parece realmente necessário porque a implementação foi substituída? Qual é o objetivo disso?

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
}

Eu tenho meu próprio bundler personalizado e estou no processo de implementação, mas não consigo ver por que seria uma necessidade absoluta ou qual seria o objetivo ... inicialmente eu suspeitei de algum uso de memória / otimização de vazamento, mas estes são apenas chamadas encaminhadas para RefreshRuntime ...

@leidegre Não posso comentar sobre a decisão de definir $ RefreshSig $ no objeto janela, mas o acoplamento a um ambiente de navegador me deu problemas ao consumir Fast Refresh no React NativeScript. @pmmmwh veio ao resgate adaptando seu plugin Fast Refresh Webpack para superar o acoplamento do Fast Refresh ao navegador (os problemas encontrados e superados foram discutidos neste tópico: https://github.com/pmmmwh/react-refresh-webpack-plugin/ questões / 79). Gostaria de saber se a abordagem usada seria de alguma utilidade para você na integração de Fast Refresh do seu bundler personalizado.

Meu bundler é principalmente um wrapper em torno do compilador TypeScript. A implementação é basicamente esta, adaptada do visitante react-refresh/babel .

Isso é apenas uma coisa simples que funciona, mas não é tão completa quanto o 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
}

Eu não tinha certeza de como usar createSignatureFunctionForTransform no início, mas é apenas uma função de fábrica que cria uma pequena máquina de estado para cada componente. Portanto, você o chama uma vez para cada função com a assinatura de gancho estática (que é apenas um valor opaco, semelhante a um hash). Você então o chama do render para terminar o trabalho de configuração.

Ele muda algo assim:

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

Nisso:

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

Observe que o visitante está incompleto. Ele lida apenas com o caso de uso mais básico.

Estou um pouco confuso com o uso de window . Não me parece realmente necessário porque a implementação foi substituída? Qual é o objetivo disso?

@leidegre

Acho que a implementação no Metro não usa window mas sim o escopo global real.

Eu não sei sobre a lógica original sobre esta implementação, mas tem sido útil pela minha experiência - ela garante que a lógica de solicitação real seja independente da lógica de atualização rápida (o que significa que react-refresh/babel transformações podem ser usadas com praticamente qualquer bundler). Tal como acontece com a troca, também atua como um guarda para garantir que os módulos que não deveriam ser processados ​​pelo tempo de execução não sejam processados:

Considere um caso em que @babel/runtime está sendo usado, o que injetará auxiliares como importações para o pacote e você deseja apenas um código HMR diferente de node_modules . Se você não inicializar auxiliares vazios primeiro e ainda atribuir auxiliares ao escopo global, um caso raro pode acontecer onde os auxiliares injetados por Babel chamarão cleanup antes que o módulo de usuário-terra realmente termine a inicialização (porque eles são filhos importações).

Esta página foi útil?
0 / 5 - 0 avaliações