React: Fast Refreshがreact-hot-loaderに取って代わった今、HMR用のアプリをどのように設定する必要がありますか?

作成日 2019年08月29日  ·  85コメント  ·  ソース: facebook/react

Dan Abramovは、Devtools v4がreact-hot-loader廃止することになると述べました: https ://twitter.com/dan_abramov/status/1144715740983046144?s

私:
私はこのフックを持っています:
require("react-reconciler")(hostConfig).injectIntoDevTools(opts);
しかし、HMRは常にそれなしで完全に機能してきました。 これは今や新しい要件ですか?

ダン:
はい、それは新しいメカニズムが使用するものです。 新しいメカニズムは「react-hot-loader」を必要としないので、更新するときまでに、そのパッケージを削除する必要があります。 (かなり侵襲的です)

ただし、DevtoolsのドキュメントにはHMRについての言及はありません。 react-hot-loaderが廃止されたので(それに伴い、 require("react-hot-loader/root").hotメソッド)、HMR用のアプリを次のように設定する必要があります。

  • ReactDOMアプリ
  • ReactNativeアプリ
  • カスタムレンダラーアプリを反応させる

react-hot-loader介してHMRをすでに設定している人のための移行ガイドに特に興味があります。

また、HMRの場合、スタンドアロンのDevtoolsを使用しているか、ブラウザー拡張機能のDevtoolsを使用しているかは重要ですか?

Question

最も参考になるコメント

さて、ここに行きます。

高速更新とは何ですか?

これは、Reactからの完全なサポートによる「ホットリロード」の再実装です。 もともとはReactNative向けに出荷されていますが、ほとんどの実装はプラットフォームに依存しません。 計画は、それを全面的に使用することです—純粋なユーザーランドソリューション( react-hot-loader )の代わりとして。

Webで高速更新を使用できますか?

理論的には、そうです、それが計画です。 実際には、誰かがそれをWeb上で一般的なバンドラー(Webpack、Parcelなど)と統合する必要があります。 私はまだそれをすることに慣れていません。 多分誰かがそれを拾いたいと思っています。 このコメントは、あなたがそれをどのように行うかについての大まかなガイドです。

それは何で構成されていますか?

高速更新は、いくつかの要素が連携して機能することに依存しています。

  • モジュールシステムの「ホットモジュール交換」メカニズム。

    • これは通常、バンドラーによっても提供されます。

    • たとえば、webpackの場合、 module.hot APIを使用するとこれを実行できます。

  • Reactレンダラー16.9.0+(例:React DOM 16.9)
  • react-refresh/runtimeエントリポイント
  • react-refresh/babel Babelプラグイン

あなたはおそらく統合の部分に取り組みたいと思うでしょう。 つまり、 react-refresh/runtimeをWebpackの「ホットモジュール交換」メカニズムと統合します。

統合はどのように見えますか?

⚠️⚠️⚠️明確にするために、これは統合を自分で実装したい人のためのガイドです。 あなた自身の注意で進んでください!

最小限にしたいことがいくつかあります。

  • バンドラーでHMRを有効にする(例:webpack)
  • Reactが16.9.0+であることを確認してください
  • Babelプラグインにreact-refresh/babelを追加します

その時点で、アプリはクラッシュするはずです。 未定義の$RefreshReg$および$RefreshSig$関数への呼び出しが含まれている必要があります。

次に、 react-dom (!)を含むアプリ内のコードの前に実行する必要がある新しいJSエントリポイントを作成する必要がありreact-dom後に実行された場合、何も機能しません。 そのエントリポイントは次のようになります。

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

これでクラッシュが修正されるはずです。 しかし、これらの$RefreshReg$$RefreshSig$実装はおっとなので、それでも何もしません。 それらを接続することは、あなたがしなければならない統合作業の要です。

それをどのように行うかは、バンドラーによって異なります。 webpackを使用すると、すべてのモジュールの実行の前後にコードを追加するローダーを作成できると思います。 または、モジュールテンプレートに何かを挿入するためのフックがあるかもしれません。 とにかく、達成したいのは、すべてのモジュールが次のようになることです。

// BEFORE EVERY MODULE EXECUTES

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

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

try {

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

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

ここでの考え方は、Babelプラグインがこの関数への呼び出しを発行し、上記の統合によってそれらの呼び出しがモジュールIDに関連付けられるというものです。 コンポーネントが登録されているときにランタイムが"path/to/Button.js Button"ような文字列を受け取るようにします。 (または、webpackの場合、IDは数字になります。)Babel変換とこのラッピングの両方が開発モードでのみ発生する必要があることを忘れないでください。

モジュールコードをラップする代わりに、バンドラーが実際にモジュールファクトリを初期化する場所の周りにこのようなtry / finallyを追加する方法があるかもしれません。 ここMetro(RNバンドラー)で行うように。 これは、すべてのモジュールを肥大化させる必要がないため、またはimporttry / finallyラップする場合など、不正な構文の導入について心配する必要がないため、おそらくより良いでしょう

これを接続すると、最後の問題が1つあります。 バンドラーは、更新を処理していることを認識していないため、とにかくページをリロードする可能性があります。 あなたはそれをしないように言う必要があります。 これもバンドラー固有ですが、私が提案するアプローチは、すべてのエクスポートがReactコンポーネントであるかどうかを確認し、その場合は更新を「受け入れる」ことです。 webpackでは、次のようになります。


// ...ALL MODULE CODE...

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

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

isReactRefreshBoundaryとは何ですか? これは、エクスポートを浅く列挙し、Reactコンポーネントのみをエクスポートするかどうかを決定するものです。 これが、更新を受け入れるかどうかを決定する方法です。 ここにコピーアンドペーストしませんでしたが、この実装は良いスタートになる可能性があります。 (そのコードでは、 Refreshreact-refresh/runtimeエクスポートを指します)。

Babelトランスフォームは関数に対して$RefreshReg$のみを呼び出すため、すべてのエクスポート

最後に、 enqueueUpdate()関数は、実際のReact更新をデバウンスして実行するモジュール間で共有されるものになります。

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

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

この時点で、何かが機能しているはずです。

ニュアンス

「FastRefresh」ブランディングに入ると私が気にかけているいくつかのベースラインエクスペリエンスの期待があります。 構文エラー、モジュール初期化エラー、またはレンダリングエラーから正常に回復できる必要があります。 これらのメカニズムについては詳しく説明しませんが、これらのケースを適切に処理するまで、実験を「高速更新」と呼ぶべきではないと強く感じています。

残念ながら、webpackがこれらすべてをサポートできるかどうかはわかりませんが、ある程度機能する状態になってから行き詰まった場合は、サポートを求めることができます。 たとえば、webpackのaccept() APIがエラー回復をより困難にしていることに気づきました(エラーの後にモジュールの_前の_バージョンを受け入れる必要があります)が、それを回避する方法があります。 もう1つ戻る必要があるのは、Babelプラグインで見つかったものだけでなく、すべてのエクスポートを自動的に「登録」することです。 今のところ、これは無視しましょう。ただし、webpackなどで機能するものがあれば、それを磨くことを検討できます。

同様に、Create React Appのreact-error-overlayと同様に、「エラーボックス」エクスペリエンスと統合する必要があります。 エラーを修正するとエラーボックスが消えるなど、微妙な違いがあります。 それはまた、財団が設置されたら私たちができるいくつかのさらなる作業を必要とします。

ご不明な点がございましたらお知らせください。

全てのコメント85件

いくつかの混乱があります。 新しいDevToolsは、ホットリロードを有効にしません(またはリロードとは何の関係もありません)。 むしろ、Danが取り組んできたホットリロードの変更は、DevToolsとReactが通信に使用する「フック」を利用します。 リロードを実行できるように、中央に追加されます。

タイトルを編集して、DevToolsの言及を削除しました(混乱を招く可能性があるため)。

新しいHMRをどのように使用すべきかという質問については、私はそこでの最新の考え方を知らないと思います。 @gaearonがCRAリポジトリで
https://github.com/facebook/create-react-app/pull/5958

新しいHMRをどのように使用すべきかという質問については、私はそこでの最新の考え方を知らないと思います。 @gaearonがCRAリポジトリで

読者のために明確にするために、そのPRは非常に時代遅れであり、もはや関連性がありません。


FastRefreshがどのように機能するかとそれを統合する方法について何か書き留める必要があります。 まだ時間がありません。

さて、ここに行きます。

高速更新とは何ですか?

これは、Reactからの完全なサポートによる「ホットリロード」の再実装です。 もともとはReactNative向けに出荷されていますが、ほとんどの実装はプラットフォームに依存しません。 計画は、それを全面的に使用することです—純粋なユーザーランドソリューション( react-hot-loader )の代わりとして。

Webで高速更新を使用できますか?

理論的には、そうです、それが計画です。 実際には、誰かがそれをWeb上で一般的なバンドラー(Webpack、Parcelなど)と統合する必要があります。 私はまだそれをすることに慣れていません。 多分誰かがそれを拾いたいと思っています。 このコメントは、あなたがそれをどのように行うかについての大まかなガイドです。

それは何で構成されていますか?

高速更新は、いくつかの要素が連携して機能することに依存しています。

  • モジュールシステムの「ホットモジュール交換」メカニズム。

    • これは通常、バンドラーによっても提供されます。

    • たとえば、webpackの場合、 module.hot APIを使用するとこれを実行できます。

  • Reactレンダラー16.9.0+(例:React DOM 16.9)
  • react-refresh/runtimeエントリポイント
  • react-refresh/babel Babelプラグイン

あなたはおそらく統合の部分に取り組みたいと思うでしょう。 つまり、 react-refresh/runtimeをWebpackの「ホットモジュール交換」メカニズムと統合します。

統合はどのように見えますか?

⚠️⚠️⚠️明確にするために、これは統合を自分で実装したい人のためのガイドです。 あなた自身の注意で進んでください!

最小限にしたいことがいくつかあります。

  • バンドラーでHMRを有効にする(例:webpack)
  • Reactが16.9.0+であることを確認してください
  • Babelプラグインにreact-refresh/babelを追加します

その時点で、アプリはクラッシュするはずです。 未定義の$RefreshReg$および$RefreshSig$関数への呼び出しが含まれている必要があります。

次に、 react-dom (!)を含むアプリ内のコードの前に実行する必要がある新しいJSエントリポイントを作成する必要がありreact-dom後に実行された場合、何も機能しません。 そのエントリポイントは次のようになります。

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

これでクラッシュが修正されるはずです。 しかし、これらの$RefreshReg$$RefreshSig$実装はおっとなので、それでも何もしません。 それらを接続することは、あなたがしなければならない統合作業の要です。

それをどのように行うかは、バンドラーによって異なります。 webpackを使用すると、すべてのモジュールの実行の前後にコードを追加するローダーを作成できると思います。 または、モジュールテンプレートに何かを挿入するためのフックがあるかもしれません。 とにかく、達成したいのは、すべてのモジュールが次のようになることです。

// BEFORE EVERY MODULE EXECUTES

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

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

try {

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

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

ここでの考え方は、Babelプラグインがこの関数への呼び出しを発行し、上記の統合によってそれらの呼び出しがモジュールIDに関連付けられるというものです。 コンポーネントが登録されているときにランタイムが"path/to/Button.js Button"ような文字列を受け取るようにします。 (または、webpackの場合、IDは数字になります。)Babel変換とこのラッピングの両方が開発モードでのみ発生する必要があることを忘れないでください。

モジュールコードをラップする代わりに、バンドラーが実際にモジュールファクトリを初期化する場所の周りにこのようなtry / finallyを追加する方法があるかもしれません。 ここMetro(RNバンドラー)で行うように。 これは、すべてのモジュールを肥大化させる必要がないため、またはimporttry / finallyラップする場合など、不正な構文の導入について心配する必要がないため、おそらくより良いでしょう

これを接続すると、最後の問題が1つあります。 バンドラーは、更新を処理していることを認識していないため、とにかくページをリロードする可能性があります。 あなたはそれをしないように言う必要があります。 これもバンドラー固有ですが、私が提案するアプローチは、すべてのエクスポートがReactコンポーネントであるかどうかを確認し、その場合は更新を「受け入れる」ことです。 webpackでは、次のようになります。


// ...ALL MODULE CODE...

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

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

isReactRefreshBoundaryとは何ですか? これは、エクスポートを浅く列挙し、Reactコンポーネントのみをエクスポートするかどうかを決定するものです。 これが、更新を受け入れるかどうかを決定する方法です。 ここにコピーアンドペーストしませんでしたが、この実装は良いスタートになる可能性があります。 (そのコードでは、 Refreshreact-refresh/runtimeエクスポートを指します)。

Babelトランスフォームは関数に対して$RefreshReg$のみを呼び出すため、すべてのエクスポート

最後に、 enqueueUpdate()関数は、実際のReact更新をデバウンスして実行するモジュール間で共有されるものになります。

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

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

この時点で、何かが機能しているはずです。

ニュアンス

「FastRefresh」ブランディングに入ると私が気にかけているいくつかのベースラインエクスペリエンスの期待があります。 構文エラー、モジュール初期化エラー、またはレンダリングエラーから正常に回復できる必要があります。 これらのメカニズムについては詳しく説明しませんが、これらのケースを適切に処理するまで、実験を「高速更新」と呼ぶべきではないと強く感じています。

残念ながら、webpackがこれらすべてをサポートできるかどうかはわかりませんが、ある程度機能する状態になってから行き詰まった場合は、サポートを求めることができます。 たとえば、webpackのaccept() APIがエラー回復をより困難にしていることに気づきました(エラーの後にモジュールの_前の_バージョンを受け入れる必要があります)が、それを回避する方法があります。 もう1つ戻る必要があるのは、Babelプラグインで見つかったものだけでなく、すべてのエクスポートを自動的に「登録」することです。 今のところ、これは無視しましょう。ただし、webpackなどで機能するものがあれば、それを磨くことを検討できます。

同様に、Create React Appのreact-error-overlayと同様に、「エラーボックス」エクスペリエンスと統合する必要があります。 エラーを修正するとエラーボックスが消えるなど、微妙な違いがあります。 それはまた、財団が設置されたら私たちができるいくつかのさらなる作業を必要とします。

ご不明な点がございましたらお知らせください。

構文エラー/初期化エラーは、Reactにレンダリングを開始するように指示する前に、何らかの方法で処理するのに「十分に簡単」である必要がありますが、レンダリングエラーはエラー境界とどのように相互作用しますか?

レンダリングエラーが発生すると、最も近いエラー境界がトリガーされ、それ自体がエラー状態にスナップショットされます。ライブ更新後に子が魔法のように修正されたことをエラー境界に伝える一般的な方法はありません。 /すべての更新可能なコンポーネントは独自のエラー境界を無料で取得する必要がありますか?それとも、初期化時にランタイムサポートが検出された場合、リコンシラーでエラー処理の動作が異なりますか?

すべてのエクスポートはReactコンポーネントであり、その場合、更新を「受け入れ」ます

そのようなコンポーネントを検出する方法はありますか? 私が理解している限りでは-いいえ。 export.toString().indexOf('React')>0除きますが、HOCを適用すると機能しなくなります。
さらに、ファイルの最後にある_self accepting_はエラーが発生しにくく、新しいacceptハンドルが確立されず、次の更新が上限にバブルするため、 require("react-hot-loader/root").hotが作成されました。

いずれにせよ、外部APIを変更せずに、すべてのreact固有のコードをreact-hot-loaderからスローする場合

react-refresh/babel 0.4.0を使用すると、多数のファイルでこのエラーが発生します。

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

私はそのファイルをそれを引き起こす最も単純なものに絞り込みました:

import { useContext } from 'react'

export default () => useContext()

レンダリングエラーが発生すると、最も近いエラー境界がトリガーされ、それ自体がエラー状態にスナップショットされます。ライブ更新後に子が魔法のように修正される可能性があることをエラー境界に伝える一般的な方法はありません。

React内の高速更新コードは、現在失敗している境界を記憶しています。 高速更新の更新がスケジュールされている場合は常に、それらを再マウントします。

境界がないが、ルートが更新に失敗した場合、Fast Refreshは、最後の要素でそのルートのレンダリングを再試行します。

ルートがマウントに失敗した場合、 runtime.hasUnrecoverableErrors()はそれを通知します。 次に、強制的にリロードする必要があります。 そのケースは後で処理できましたが、まだ修正する時間がありませんでした。

react-refresh / babel 0.4.0を使用すると、多数のファイルでこのエラーが発生します。

新しい問題を提出しますか?

そのようなコンポーネントを検出する方法はありますか?

Runtime.isLikelyAReactComponent()使用する実装にリンクしました。 完璧ではありませんが、十分です。

新しい受け入れハンドルは確立されず、次の更新はより高い境界にバブルします

例を挙げていただけますか? 私はフォローしていません。 とにかく、それはバンドラーに固有のものです。 私はMetroにやりたいことをさせました。 APIがない場合は、webpackに何かを追加するように依頼できます。

ここでの目標は、一貫性を保証しながら、できるだけ少ないモジュールを再実行することです。 ほとんどの編集では、更新をルートにバブルしたくありません。

すべてのreact固有のコードをreact-hot-loaderからスローし、外部APIに影響を与えないようにすると思われます

たぶん、トップレベルのコンテナも削除したいのですが。 また、エラーボックスとの緊密な統合も必要です。 多分それはまだreact-hot-loaderと呼ぶことができます。

ちなみに、私はガイドを編集して、忘れていた欠落部分、つまりperformReactRefresh呼び出しを含めました。 それが実際に更新をスケジュールするものです。

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

私はそのような論理では安全だとは思わないでしょう。 すべてのCapitalizedFunctionsがほとんど常にReactコンポーネントである場合でも、(私の)多くのモジュールには他のエクスポートもあります。 たとえば、_exports-for-tests_。 これは問題ではありませんが、いくつかの_予測不可能性_を作成します-ホット境界はいつでも作成される可能性があります...または1行の変更後に作成されません。
isLikelyComponentTypeテストを破ることができるもの:

  • エクスポートされたmapStateToProps (テスト用、製品コードでは使用されません)
  • エクスポートされたhook (そしてそれは大丈夫です)
  • エクスポートされたClassは、reactクラスではない可能性があります(そうではありませんが、すべきです)

つまり、ホット境界が確立されるが確立されない場合もあれば、ホット境界が確立されるが確立されない場合もあります。 古き良き不安定なホットリロードのように聞こえますが、どちらもあまり好きではありません:)

ホット境界の適用がそれほど予測不可能ではなく、かなり期待される場所が1つあります。それは、 thingまたはdomain境界、またはディレクトリインデックス、つまりindex.js再エクスポートです。同じディレクトリ内のComponent.jsからの「パブリックAPI」(Facebookスタイルのafaikではありません)。

言い換えれば、Metroで行ったのと同じですが、より多くの制限が適用されます。 遅延ロードされたコンポーネントに対してそのような境界を確立するためのリンティングルールのような他のすべては、正しい動作を強制するために使用できます。

そういえば、ホットファストリフレッシュはレイジーを処理しますか? importの反対側からの境界があると予想されますか?

ブラウザで魔法を簡単に見てみてください。とても素敵です:)私は可能な限り単純なことをしました。つまり、すべてのインストルメンテーションコードをハードコーディングしたので、webpackプラグインはまだありません。

Kapture 2019-09-07 at 23 09 04

ここのリポジトリ: https

好奇心が強いですが、webpackの場合、try / finalをラップするためのbabelプラグインがありませんか? 試してみる前に、何かを見逃していないことを確認したいだけです。

Babelプラグインは環境固有ではありません。 そのままにしておきたいです。 モジュールや更新の伝播メカニズムについては何も知りません。 それらはバンドラーによって異なります。

たとえば、Metroでは、try / finallyラッピング変換はまったくありません。 代わりに、モジュールファクトリを呼び出す場所の周りのバンドラーランタイム自体にtry / finallyを配置します。 これはwebpackでも理想的ですが、そのようにランタイムに接続できるかどうかはわかりません。

もちろん、ラッピング用に別のBabelプラグインを作成することもできます。 しかし、それはwebpackを介してそれを行うこと以上にあなたに何も買わない。 とにかくそれはwebpack固有なので。 また、そのBabelプラグインを、意味をなさない別の環境(webpackではない)で誤って実行する可能性があることは混乱を招く可能性があります。

compilation.mainTemplate.hooks.requireウォーターフォールフックにフックすることでできます。 前回の呼び出しは__webpack_require__関数のデフォルトの本体であるため、フックを利用してコンテンツをtry/finallyブロックにラップできます。

問題は、 __webpack_require__内でReactへの参照を取得することです。 可能ですが、ある程度の再入可能性と再帰性のガードが必要になる場合があります。

詳細については、webpackソースコードのMainTemplate.jsweb/JsonpMainTemplatePlugin.jsしてください。 JsonpMainTemplatePlugin自体は、 MainTemplate.jsからのフックの束を利用するだけなので、おそらくそれが取り組む必要のある「肉」です。

これは、ダンが上で概説したことを効果的に実行する、私が一緒にハッキングした頭の悪いプロトタイプです。 それはひどく不完全ですが、webpackでのlo-fi実装を証明しています

いくつかの注意:

  • react-domはここでハードコードされているため、カスタムレンダラーやサブパッケージ( react-dom/profiling )では機能しません。
  • webpackのすべてのテンプレートバリアントがどのように機能するかについてはあまり深く調べていませんが、モジュールの実行をラップする方法はかなりハックです。 たとえば、 umdライブラリターゲットを使用した場合に、この例が機能するかどうかはわかりません。

問題は、__ webpack_require__内でReactへの参照を取得することです。 可能ですが、ある程度の再入可能性と再帰性のガードが必要になる場合があります。

リフレッシュランタイムへの参照を取得することを意味していると思います。

Metroでは、できるだけ早くrequire.Refresh = RefreshRuntimeを実行することでこれを解決しました。 次に、 require実装内で、 require関数自体からプロパティを読み取ることができます。 すぐに利用できるようにはなりませんが、十分に早く設定しても問題ありません。

@maisanoいくつかの変更が必要でしたが、最終的にwebpackによって呼び出される.accept関数が表示されません。 .accept(module.i, () => {}).accept(() => {})両方を試しました(これがwebpackで機能しないことを除いて、自己受容型です)。 hotプロパティが有効になっていますが、承認されたモジュールを介して実行されます。

そのため、自己受容モジュールを呼び出すようにwebpackにパッチを適用することになり、それが最終的な修正でした。

パッチは次のとおりです。

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

これが「errorCallback」であることを望んでいる彼らのAPIに反することを私は知っています、私は特に何年も前に私たちの内部HMRに取り組んでこれに遭遇したことを覚えています、そして最終的に私たちは私たち自身のバンドラーを書くことになりました。 パーセルは「自己受け入れ」コールバックAPIをサポートしていると思います。 おそらく、webpackで問題を開いて、それをマージできるかどうかを確認する価値がありますか? @sokra

だから...私は@maisanoの仕事に基づいてプラグインをさらに洗練し
https://github.com/pmmmwh/react-refresh-webpack-plugin
(私が始めたときにwebpackの内部をいじっているとは思わないので、TypeScriptで作成しました。それをプレーンなJS / Flowに変換できます)

webpack Dependencyクラスでホットモジュールコードを挿入するためのローダーの必要性を排除しようとしましたが、すべてのモジュールの再解析が必要になるようです(すべての関数がインラインであっても、まだ必要ですどこかでreact-refresh/runtimeへの参照)。

もう1つの問題は、webpack内のJavaScriptのようなファイルを検出する簡単な方法(afaik)がないことです。たとえば、 html-webpack-pluginjavascript/autoタイプも使用するため、次のように見えるものをハードコーディングしました。ローダーインジェクションに使用できるファイルマスク(JS / TS / Flow)。

また、この5年前のスレッドの@gaearonからのコメントに基づいて、エラー回復(少なくとも構文エラー)を追加しました。 次は、反応エラーからの回復です-これは、グローバルエラー境界( react-hot-loader AppWrapperのようなもの)を挿入することで実行できると思います。これは、エラーボックスインターフェイスにも取り組みますが、しませんでした。まだそれに到達する時間があります。

@natewによってこれは、 enqueueUpdate呼び出しとhot.accpet(errorHandler)呼び出しを分離することで実現されます。

@pmmmwhなんてタイミング! 要旨で共有した作業の一部を構築/微調整したリポジトリを作成しました。

ここのプラグインは私が取った最初のアプローチよりも少ししっかりしていますが、私はどのような場合でもエラー処理に到達していません。

次はreactエラーからの回復です-これはグローバルエラー境界(react-hot-loaderのAppWrapperのようなもの)を挿入することで実行できると思います。これもエラーボックスインターフェイスに取り組みますが、到達する時間がありませんでしたそれはまだかなりです。

それはすでに箱から出してうまくいくはずです。 カスタムエラー境界やここでの折り返しは必要ありません。

次はreactエラーからの回復です-これはグローバルエラー境界(react-hot-loaderのAppWrapperのようなもの)を挿入することで実行できると思います。これもエラーボックスインターフェイスに取り組みますが、到達する時間がありませんでしたそれはまだかなりです。

それはすでに箱から出してうまくいくはずです。 カスタムエラー境界やここでの折り返しは必要ありません。

@gaearonストレンジ。 関数コンポーネントのレンダリングでエラーをスローしようとしました-エラーがreturnで発生した場合、HMRは機能しますが、他の場所で発生した場合、機能しないことがあります。

@pmmmwhなんてタイミング! 要旨で共有した作業の一部を構築/微調整したリポジトリを作成しました。

ここのプラグインは私が取った最初のアプローチよりも少ししっかりしていますが、私はどのような場合でもエラー処理に到達していません。

@maisano何と言ったらいいの? 私は実際にこれに取り組み始め、先週末に依存性注入の問題で立ち往生しました...あなたの要点は私に道を提供してくれました:tada:

代わりにエラーが発生した場合、HMRは機能しますが、他の場所で発生した場合、機能しない場合があります。

あなたが正確に試したことと、「うまくいく」と「うまくいかない」とはどういう意味かについて、もっと詳しく知りたいと思います。

モジュールバンドラーの統合が正しく実装されていない場合(トピックまたはこのスレッド)、うまくいかないことがたくさんあります。 React自体には、編集中に発生したエラーからの回復を妨げるものは何もないと思います。 React Native 0.61RC3で動作することを確認できます。

@ pmmmwh@ maisano次のチェックでは、名前付きエクスポートとしてコンポーネントを含むモジュールがスキップされ、更新境界が確立されません。

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

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

Metroでこれが必要な理由はわかりませんが、 webpackゲッターは名前付きのエクスポートを返すだけで、私が知る限り、副作用はありません。 したがって、安全に削除できるはずです。

@gaearon React.lazyコンポーネント(コード分割ルートなど)は再レンダリングされていません。 それは仕様によるものですか? 更新境界が確立されていることがわかりますが、 performReactRefresh()は何もしていないようです。 怠惰な子供への変更はうまく更新されるので、これは大したことではありませんが、私たちは何か間違ったことをしているのだろうかと思っています...

lazyは小さなステートマシンです。古いコンポーネントへの参照を保持しているため、その参照を更新する必要があります。
今、それがそうであったと想像してみましょう、そして今は真新しいlazyオブジェクトをloadingフェーズを考えなければならず、それはおそらくすべてのネストされたツリーを破壊するでしょう。

私は怠惰な仕事を期待します。 多分何かが壊れた。 再現ケースを見る必要があります。

いくつかのプロトタイプがあったので、1つを選び、この議論をその問題に移すべきでしょうか? そしてそこで繰り返します。

有る:
https://github.com/maisano/react-refresh-plugin
そして:
https://github.com/pmmmwh/react-refresh-webpack-plugin
[email protected]で動作するpmmmwhのプラグインのフォークを設定しました(名前付きエクスポートも修正します):
https://github.com/WebHotelier/webpack-fast-refresh

react-hot-loaderどうですか?

react-hot-loaderfast refreshからほとんどすべての機能をバックポートしましたが、すべてをバックポートできない歴史的および統合的な瞬間はほとんどなく、正直なところ、「rhl」の用語でそれらを再実装する意味はありません。 。 だから-それを引退させてください。

もう1つ: https

あなたが正確に試したことと、「うまくいく」と「うまくいかない」とはどういう意味かについて、もっと詳しく知りたいと思います。

モジュールバンドラーの統合が正しく実装されていない場合(トピックまたはこのスレッド)、うまくいかないことがたくさんあります。 React自体には、編集中に発生したエラーからの回復を妨げるものは何もないと思います。 React Native 0.61RC3で動作することを確認できます。

いくつかの調整を行った後、それが機能することを確認できます。

ただし、babelプラグインがクラスで機能していなかったようです。 私がチェックしたところ、これは実装に関係なく発生しているようです。挿入されたすべてのコードとreact-refresh/runtimeは正しく機能するからです。 これが意図されているのか、それともwebpack固有のものなのかはわかりません。後者の場合は、明日修正を試みることができます。 (これもメトロプリセットのみでテストしました。要点をここで再現して

RNで動作することは確かですが、現在のマシンではRNでテストするのに便利な環境がないため、メトロでのbabelプラグインの実装または非常に役立つ変換を教えていただければと思います。

いくつかのプロトタイプがあったので、1つを選び、この議論をその問題に移すべきでしょうか? そしてそこで繰り返します。

たぶんここに行けのWebPACK @ 5、@apostolosによってフォークとの新しいHMRロジック読んだ後にWebPACKの@の次に、修正はストレートフォワードでなければなりません。

はい、Babelプラグインはクラスを登録しません。 これは、モジュールシステムレベルで発生することを目的としています。 各エクスポートは、Reactコンポーネントである可能性が高いかどうかを確認する必要があります。 (チェック機能はランタイムによって提供されます。)trueの場合、Babelプラグインと同じようにエクスポートを登録します。 filename exports%export_nameようなIDを付けます。 Babelプラグインがクラスを検出しないため、これがMetroでクラスを機能させるものです。

言い換えると、とにかくクラスの状態を保持できないため、変換を使用してソースコード内でインラインで検索するのではなく、モジュールのエクスポート境界に「配置」することをお勧めします。 エクスポートは、プラグインで見つからなかったコンポーネントの「キャッチオール」として機能する必要があります。

Mailchimpは、最後に共有したプラグインのフォークを使い始めました。 それはもう少し肉付けされており、それを使用することを選択した人々は非常に満足しているようです。 これからもローカルで繰り返します。 フォークを削除し、少し進んだらアップストリームで更新を公開する予定です。

@gaearon更新しても安全であることがわかっているものに添付できるシンボルを追加すること

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

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

createはオブジェクトを返すだけです。 今のところパッチを適用しましたが、デフォルトのエクスポートオブジェクトに、これが安全なファイルであることを示す記号を簡単に追加できます。 最適なパターンが正確にわからない。

編集:これが更新の実装に入る可能性があることに気づきました! ランタイムの方が良いかもしれないと思いましたが、おそらくそうではありません。 ローダーの非常に多くの異なる実装があるので、標準的な方法がある方が良いかもしれません。

10年先に進みましょう。 コードベースはどのように見えますか? ここで許可し、そこで禁止しますか? これらのフラグを最新の状態に保つ方法は? どうやって推論するのですか? 場所を更新するのに安全で安全でない場所があるように、保存する必要があるか、何らかの理由で適切に調整できません。 それぞれの場合の正当な理由はどれですか?

  • どのsymbolsがもっとありますか-約force allowリロード、またはforce disallowリロード
  • 更新の伝播境界を低くする(つまり、「this」モジュール境界で更新を受け入れる)か、上げる(つまり、「that」モジュール境界で更新を受け入れる)必要がある理由
  • 境界が設定されない場合はどうなりますか? それはパフォーマンスの問題だけですか、それとももっと深刻なことが起こる可能性がありますか?

こんにちは皆さん👋私はここで手を貸したいと思っています。 単一のレポ/取り組みに同意しましたか?

このリポジトリは@pmmmwhによって共有されていますか?
https://github.com/pmmmwh/react-refresh-webpack-plugin

それとも、このリポジトリは@maisanoによって共有されていますか?
https://github.com/maisano/react-refresh-plugin

@pmmmwhによる

Parcel 2での実装はここから始まりました: https

夏!

それを探している人のために、開発にNollupを使用したRollupプロジェクトのReact Refreshの実装: https

おそらく最もクリーンな実装ではありませんが、機能します。

webpackソリューションの場合、上記のプラグインの公式リリースはないようです。したがって、reactに最適なHMRソリューションは、Danのライブラリです: https

箱から出して高速リフレッシュをサポートするParcel2 alpha3を出荷しました。 お気軽にお試しください。 😍https //twitter.com/devongovett/status/1197187388985860096 = 20

🥳RHLに非推奨のメモを追加しました🥳

@pmmmwh進行中react-app-rewired 、およびcustomize-craを使用してCRAアプリでこれを試すために使用してきたレシピ:

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

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

./package.json編集します:

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

./config-overrides.jsファイルを追加します。

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

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

これまでの経験を楽しんでいます。 関係者の皆様のご尽力に感謝いたします!

ありがとう@ drather19

私はあなたの指示に基づいてリポジトリを作成しました、それは機能します:https://github.com/jihchi/react-app-rewired-react-refresh誰かが試して入力を節約したい場合は、リポジトリのクローンを作成してください。


https://github.com/pmmmwh/react-refresh-webpack-plugin/tree/master/examples/cra-kitchen-sinkを参照して

そして... Webpackのv0.1.0が出荷されました🎉

@ drather19 @jihchi
そのバージョンに切り替えたいと思うかもしれません-これには、より統一されたエクスペリエンスと、初期実装での多くのバグ修正が含まれています。

@pmmmwhts-loader + babel-loaderサポートしますか?

@pmmmwhts-loader + babel-loaderサポートしますか?

私はBabelのみでTSに対してテストを行いましたが、機能するので、ts + babelローダーを使用しても機能しない場合はお気軽に問題を報告してください:)

@ drather19リポジトリのクローンを作成して実行しようとしましたが、

環境、
OS-OSX 10.14.6
ノード-v12.13.0
毛糸-1.19.2

@ pmmmwh-参考までに

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


@ drather19リポジトリのクローンを作成して実行しようとしましたが、

環境、
OS-OSX 10.14.6
ノード-v12.13.0
毛糸-1.19.2

@ pmmmwh-参考までに

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

これはプラグインのmasterブランチで修正されており、明日リリースされる予定です。

@pmmmwhのwebpackプラグインを、babelを使用してTypeScriptReactアプリで動作させることができ

@IronSeanそのプラグインのリポジトリで報告してください。 これは正常に聞こえません。

私はこれで遊んで、パフォーマンスを近づけるbabel config側に何かが欠けているかどうかを確認しますが、今のところ、ts-loaderと完全な更新と比較するとウォッシュです。

そこに設定/設定を投稿してみませんか? これ以上の文脈がなければ、問題を理解することはできません。

@pmmmwhこの問題を開いて、実際にプラグインが違いを
https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/20

react-refreshReact Fast Refresh ?)はPreactで機能しますか、それともreact-hot-loaderはPreactの長期的なソリューションですか?

Preactに依存する@Jumblemuddleですが、必要に応じてFastRefreshと統合できるはずです。

Fast Refreshで実行したいCRAの人々のために、私は次のcraco.config.js介してcraco(vs。react-app-rewired + customize-cra)でより良い運がありました:

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

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

特に、 webpackConfig.optimization.runtimeChunk = false;を追加すると、フックを追加/削除しても、正常に高速に更新できます。

改善されたエクスペリエンスをさらに楽しんでいます。 https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/25経由のヒントを提供してくれたます。 (<-解決しました!)

@ drather19の提案に基づいてcraプラグインを公開しました。 esetnik / customize-cra-react-refreshを参照してください。

@ drather19のおかげで、コードを少し変更して、yarnワークスペースのmonorepoセットアップで機能できるようになりました。

まず、高速更新を有効にするサブパッケージに次のものをインストールします。

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

次に、これをcraco.config.js追加します。

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

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

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

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

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

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

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

@gaearonある時点で、デフォルトで高速更新がCRAで利用可能になると予想していますか?
もしそうなら、そのために何が必要ですか?

そのためにはある程度の作業が必要です:-)現在行われています。

HMR関数を使用する場合は呼び出されますか? たとえば、componentDidMountです。
私はreact-proxyを使用し、componentDidMountが呼び出されます。
そしてreact15.XはFastRefreshを使用できますか?

  • componentDidMountが呼び出されます。 unmount同様に、クラスは完全にリロードされます。
  • そして、 react-proxy使用をやめる良い
  • 15.X ? - 絶対違う。 Fast Refresh __はreactの一部__であるため、最新バージョンにのみ存在します。

したがって、react-proxyの代わりにFastRefreshまたはreact-hot-loaderを使用する必要がありますか?
関数(componentDidMount)がHMRに対して実行されないようにする方法はありますか? -メソッドを呼び出して新しいデータを取得します。

JITでreact-hot-loaderをどのように使用する必要がありますか? -ブラウザのリアルタイムコンパイル

  • したがって、react-proxyの代わりにFastRefreshまたはreact-hot-loaderを使用する必要がありますか?

    最初にfast refresh試してから、次にRHL試してください

  • 関数(componentDidMount)がHMRに対して実行されないようにする方法はありますか? -メソッドを呼び出して新しいデータを取得します。

    (フックを使用...)、コンポーネントのライフサイクルに依存せず、必要に応じてデータをフェッチします。 react-queryswrまたは他の解決策を試してください。

新しいHMRをどのように使用すべきかという質問については、私はそこでの最新の考え方を知らないと思います。 @gaearonがCRAリポジトリで

読者のために明確にするために、そのPRは非常に時代遅れであり、もはや関連性がありません。

FastRefreshがどのように機能するかとそれを統合する方法について何か書き留める必要があります。 まだ時間がありません。

今日の時点で、そのPRはまだ開いています。 まだマージされる可能性のある関連するPRだけを開いたままにして、概要を把握しておくとよいでしょう。 それらを参照として保持している場合は、ブランチ、タグ、または別のリポジトリに移動することをお勧めします。

Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly.を取得し続けますが、ドキュメントに従いましたが、何かを見逃したようです。

エラーが発生し続けます:[React Refresh]ホットモジュール交換(HMR)が有効になっていません! React Refreshを正しく機能させるには、HMRが必要です。 ドキュメントに従いましたが、何かを見逃したようです。

@silkfirewebpackプラグインを使用していると思います。 はいの場合は、webpackプラグインリポジトリhttps://github.com/pmmmwh/react-refresh-webpack-plugin/に質問を提出して

今日の時点で、そのPRはまだ開いています。 まだマージされる可能性のある関連するPRだけを開いたままにして、概要を把握しておくとよいでしょう。 それらを参照として保持している場合は、ブランチ、タグ、または別のリポジトリに移動することをお勧めします。

あなたの提案に感謝しますが、何千もの未読の通知があると、古いPRを再訪することを覚えておくのが難しい場合があります。 私は、Create React Appリポジトリのメンテナが正しいことを行い、それがもはや役に立たないと判断した場合は閉じると信じています。

これを閉じます。

webpackのリファレンス実装としてhttps://github.com/pmmmwh/react-refresh-webpack-plugin/があり
また、 https: //github.com/facebook/react/issues/16604#issuecomment -528663101では、カスタム統合を行う方法について説明しています。

Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly.を取得し続けますが、ドキュメントに従いましたが、何かを見逃したようです。

webpackHMRを有効にしていないようです。 さらにヘルプが必要な場合は、プラグインのリポジトリに問題を報告してください。

ホットリプレースメントはReactの一部になっているため、Reactのドキュメントに別の場所がある場合は、特定のバンドラーやプラットフォームで使用する追加のライブラリを示し、自己更新型のcssなどの既存の落とし穴について説明します。モジュール。

このような情報は、githubの問題やブログ投稿に埋め込まれるべきではありません。

@theKasheyはReactにありますが、react-domの実装は実験的なものにすぎません。
また、create-react-appにバンドルされる高速更新の実装がありますが、まだリリースされていません:pmmmwh / react-refresh-webpack-plugin#7。 おそらくそれは次のreact-scriptsバージョンになるでしょう。

したがって、おそらくReactチームは、この実験段階で、react-domのFastRefreshについて話すのはまだ正しいとは感じていません。

Reactにありますが、react-domの実装は実験的なものにすぎません。

明確にするために、 react-dom自体の実装は、React Nativeの場合と同様に、安定しています。 統合がすべて安定しているわけではないというだけです。

Reactのドキュメントに別の場所があり、特定のバンドラーやプラットフォームで使用される追加のライブラリを示し、自己更新型のcssモジュールなどの既存の落とし穴について説明している必要があります。

これは合理的に聞こえます。 おそらく同様のRNページに基づいて、PRを上級ガイドセクションに追加していただければ幸い

@gaearon
私のreactアプリは、いくつかのスタイル付きコンポーネントの変更で問題なく、それらの変更を問題なく正しく適用できます。
ただし、Reduxのレデューサーで一部のコードを変更すると、アプリ全体がハードリフレッシュされ、すべてのredux状態が失われます。
react-fast-refreshと一緒に現在の状態を保存するために、 redux-persistような他のライブラリを使用する必要がありますか?

私たちは一周しました、そしてここで私たちは再び行きます😅

これが低レベルのHMRの機能であり、高速更新の責任の範囲外です。 reduxまたはwebpackのドキュメントを参照してください

私たちは一周しました、そしてここで私たちは再び行きます😅

これが低レベルのHMRの機能であり、高速更新の責任の範囲外です。 reduxまたはwebpackのドキュメントを参照してください

完全な円の参照をリンクしますか?

@ jwchang0206ストアにこのようなコードがあることを確認してください。

完全な円の参照をリンクしますか?

React HotLoaderについても同じ質問がありました。 同じ答えが与えられました。 私たちは新しいサイクルの始まりにあります。

@ jwchang0206この問題に対処するために私が書いた小さなライブラリであるredux- reducers - injectorを見てください。
これにより、ホットリロードによるレデューサーのリロードをサポートできるようになります。
レデューサーの不変性のreduxの原則に従うようにしてください。そうすれば、スムーズに機能します💯
また、 sagasを使用している場合は、

@gaearon windowの使用に少し混乱しています。 実装がスワップアウトされているので、本当に必要であるようには見えませんか? そのポイントは何ですか?

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

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

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

私は自分のカスタムバンドラーを持っていて、これを実装するプロセスですが、なぜそれが絶対に必要なのか、それが何を意味するのかわかりません...最初はメモリ使用量/リークの最適化を疑っていましたが、これらはRefreshRuntime転送されるコールです...

@leidegreウィンドウオブジェクトに$ RefreshSig $を設定する決定についてコメントすることはできませんが、ブラウザー環境への結合により、ReactNativeScriptで高速更新を使用するときに問題が発生しました。 @pmmmwhは、Fast Refresh Webpackプラグインを適応させて、Fast Refreshのブラウザーへの結合を克服することで救助に来ました(発生して克服した問題については、このスレッドで説明しました:https://github.com/pmmmwh/react-refresh-webpack-plugin/問題/ 79)。 使用されているアプローチは、カスタムバンドラーのFastRefreshの統合に役立つのではないかと思います。

私のバンドラーは主にTypeScriptコンパイラーのラッパーです。 実装は主にこれであり、 react-refresh/babel訪問者から適応されています。

これは機能する単純なことですが、 react-refresh/bable訪問者ほど完全ではありません。

import ts = require("typescript")

import { IndexModule } from "./registry"

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

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

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

      let hasComponents = false

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

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

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

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

            hasComponents = true

            let hookSignatureString = ""

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

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

            const signature = ts.createUniqueName("s")

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

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

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

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

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

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

        return node
      }

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

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

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

        // insert after

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

      return sourceFile
    }
  }
}

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

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

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

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

          return text
        }
      }
    }
  }
  return undefined
}

最初はcreateSignatureFunctionForTransform使い方がわかりませんでしたが、コンポーネントごとに小さなステートマシンを作成するのは単なるファクトリ関数です。 したがって、静的フックシグネチャ(ハッシュに似た不透明な値)を使用して、関数ごとに1回呼び出します。 次に、レンダリングから呼び出して、セットアップ作業を完了します。

次のように変更されます。

import React = require("react")

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

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

これに:

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

訪問者が不完全であることに注意してください。 最も基本的なユースケースのみを扱います。

windowの使用に少し混乱しています。 実装がスワップアウトされているので、本当に必要であるようには見えませんか? そのポイントは何ですか?

@leidegre

Metroでの実装では、 windowではなく、実際のglobalスコープを使用していると思います。

この実装の元の理論的根拠についてはわかりませんが、私の経験からは役に立ちました-実際のrequireロジックが高速リフレッシュロジックから独立していることを保証します(つまり、 react-refresh/babel変換は事実上すべてのバンドラー)。 スワッピングと同様に、ランタイムによって処理されるはずのないモジュールが処理されないようにするためのガードとしても機能します。

@babel/runtimeが使用されている場合を考えてみましょう。これにより、バンドルへのインポートとしてヘルパーが挿入され、 node_modulesコードのみをHMRする必要があります。 最初に空のヘルパーを初期化せずに、ヘルパーをグローバルスコープに割り当てると、ユーザーランドモジュールが実際に初期化を完了する前に、バベルが挿入したヘルパーがcleanupを呼び出すというまれなケースが発生する可能性があります(子であるためインポート)。

このページは役に立ちましたか?
0 / 5 - 0 評価