React: Bagaimana kita harus menyiapkan aplikasi untuk HMR sekarang karena Fast Refresh menggantikan react-hot-loader?

Dibuat pada 29 Agu 2019  ·  85Komentar  ·  Sumber: facebook/react

Dan Abramov menyebutkan bahwa Devtools v4 akan menghasilkan react-hot-loader usang: https://twitter.com/dan_abramov/status/1144715740983046144?s=20

Saya:
Saya memiliki pengait ini:
require("react-reconciler")(hostConfig).injectIntoDevTools(opts);
Tetapi HMR selalu bekerja sepenuhnya tanpa itu. Apakah sekarang ini merupakan persyaratan baru?

Dan:
Ya, itulah yang digunakan mekanisme baru tersebut. Mekanisme baru ini tidak memerlukan "react-hot-loader" jadi pada saat Anda memperbarui, Anda ingin menghapus paket itu. (Ini cukup invasif)

Namun, saya tidak dapat melihat HMR disebutkan dalam dokumentasi Devtools; sekarang react-hot-loader telah menjadi usang (dan dengan itu, metode require("react-hot-loader/root").hot ), bagaimana kita harus mengatur aplikasi untuk HMR di:

  • Aplikasi React DOM
  • Aplikasi React Native
  • Bereaksi aplikasi perender khusus

Saya akan sangat tertarik dengan panduan migrasi khusus untuk siapa saja yang sudah menyiapkan HMR melalui react-hot-loader .

Selain itu, untuk HMR, apakah penting apakah kita menggunakan Devtools mandiri atau Devtools ekstensi browser?

Question

Komentar yang paling membantu

Oke, ini dia.

Apa itu Fast Refresh?

Ini adalah implementasi ulang "hot reloading" dengan dukungan penuh dari React. Ini awalnya dikirim untuk React Native tetapi sebagian besar implementasinya tidak bergantung pada platform. Rencananya adalah untuk menggunakannya di seluruh papan - sebagai pengganti solusi murni userland (seperti react-hot-loader ).

Dapatkah Saya Menggunakan Penyegaran Cepat di Web?

Secara teoritis, ya, itulah rencananya. Praktisnya, seseorang perlu mengintegrasikannya dengan bundler yang umum di web (mis. Webpack, Parcel). Saya belum sempat melakukan itu. Mungkin seseorang ingin mengambilnya. Komentar ini adalah panduan kasar bagaimana Anda akan melakukannya.

Terdiri Dari Apa?

Fast Refresh mengandalkan beberapa bagian yang bekerja bersama:

  • Mekanisme "penggantian modul panas" dalam sistem modul.

    • Itu biasanya juga disediakan oleh bundler.

    • Misalnya di webpack, module.hot API memungkinkan Anda melakukan ini.

  • React renderer 16.9.0+ (mis. React DOM 16.9)
  • react-refresh/runtime titik masuk
  • react-refresh/babel Plugin Babel

Anda mungkin ingin mengerjakan bagian integrasi. Yaitu mengintegrasikan react-refresh/runtime dengan mekanisme "penggantian modul panas" Webpack.

Seperti Apa Integrasi Itu?

⚠️⚠️⚠️ JELAS, INI ADALAH PANDUAN BAGI ORANG YANG INGIN MELAKSANAKAN INTEGRASI DIRI. LANJUTKAN DENGAN HATI-HATI ANDA SENDIRI!

Ada beberapa hal yang ingin Anda lakukan secara minimal:

  • Aktifkan HMR di bundler Anda (mis. Webpack)
  • Pastikan React adalah 16.9.0+
  • Tambahkan react-refresh/babel ke plugin Babel Anda

Pada saat itu aplikasi Anda akan mogok. Ini harus berisi panggilan ke fungsi $RefreshReg$ dan $RefreshSig$ yang tidak ditentukan.

Kemudian Anda perlu membuat titik masuk JS baru yang harus dijalankan sebelum kode apa pun di aplikasi Anda , termasuk react-dom (!) Ini penting; jika berjalan setelah react-dom , tidak ada yang akan berhasil. Titik masuk itu harus melakukan sesuatu seperti ini:

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

Ini harus memperbaiki crash. Tetapi masih tidak akan melakukan apa-apa karena implementasi $RefreshReg$ dan $RefreshSig$ adalah noops. Menghubungkan mereka adalah inti dari pekerjaan integrasi yang perlu Anda lakukan.

Bagaimana Anda melakukannya tergantung pada bundler Anda. Saya kira dengan webpack Anda bisa menulis sebuah loader yang menambahkan beberapa kode sebelum dan setelah setiap modul dijalankan. Atau mungkin ada pengait untuk memasukkan sesuatu ke dalam template modul. Terlepas dari itu, apa yang ingin Anda capai adalah setiap modul terlihat seperti ini:

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

Idenya di sini adalah bahwa plugin Babel kami memancarkan panggilan ke fungsi ini, dan kemudian integrasi kami di atas mengikat panggilan tersebut ke ID modul. Sehingga runtime menerima string seperti "path/to/Button.js Button" ketika sebuah komponen sedang didaftarkan. (Atau, dalam kasus webpack, ID adalah angka.) Jangan lupa bahwa transformasi Babel dan penggabungan ini hanya boleh terjadi dalam mode pengembangan.

Sebagai alternatif untuk membungkus kode modul, mungkin ada beberapa cara untuk menambahkan percobaan / akhirnya seperti ini di sekitar tempat bundler benar-benar menginisialisasi pabrik modul. Seperti yang kami lakukan di sini di Metro (RN bundler). Ini mungkin akan lebih baik karena kita tidak perlu membengkak setiap modul, atau khawatir tentang memperkenalkan sintaks ilegal, misalnya saat membungkus import dengan try / finally .

Setelah Anda menghubungkannya, Anda memiliki satu masalah terakhir. Pemaket Anda tidak tahu bahwa Anda menangani pembaruan, jadi mungkin tetap memuat ulang halaman. Anda harus mengatakan untuk tidak melakukannya. Ini lagi-lagi khusus untuk bundler, tetapi pendekatan yang saya sarankan adalah memeriksa apakah semua ekspor adalah komponen React , dan dalam hal ini, "terima" pembaruan. Di webpack, ini bisa terlihat seperti:


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

Apakah isReactRefreshBoundary ? Ini adalah hal yang menghitung ekspor secara dangkal dan menentukan apakah hanya mengekspor komponen React. Begitulah cara Anda memutuskan apakah akan menerima pembaruan atau tidak. Saya tidak menyalinnya di sini tetapi penerapan ini bisa menjadi awal yang baik. (Dalam kode itu, Refresh mengacu pada react-refresh/runtime ekspor).

Anda juga ingin mendaftarkan semua ekspor secara manual karena Babel transform hanya akan memanggil $RefreshReg$ untuk fungsi. Jika Anda tidak melakukannya, pembaruan kelas tidak akan terdeteksi.

Terakhir, fungsi enqueueUpdate() akan menjadi sesuatu yang dibagi antara modul yang melakukan debounce dan melakukan update React yang sebenarnya.

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

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

Pada titik ini, Anda sudah memiliki sesuatu yang berfungsi.

Nuansa

Ada beberapa ekspektasi pengalaman dasar yang saya pedulikan yang mengarah ke branding "Penyegaran Cepat". Itu harus dapat pulih dengan baik dari kesalahan sintaks, kesalahan inisialisasi modul, atau kesalahan rendering. Saya tidak akan membahas mekanisme ini secara mendetail, tetapi saya sangat yakin Anda tidak boleh menyebut eksperimen Anda "Penyegaran Cepat" hingga eksperimen tersebut menangani kasus tersebut dengan baik.

Sayangnya, saya tidak tahu apakah webpack dapat mendukung semua itu, tetapi kami dapat meminta bantuan jika Anda mendapatkan status yang agak berfungsi tetapi kemudian macet. Misalnya, saya perhatikan bahwa API accept() webpack membuat pemulihan kesalahan lebih sulit (Anda perlu menerima versi modul _previous_ setelah kesalahan), tetapi ada cara untuk meretasnya. Hal lain yang perlu kita bahas adalah secara otomatis "mendaftarkan" semua ekspor, dan bukan hanya yang ditemukan oleh plugin Babel. Untuk saat ini, mari abaikan ini, tetapi jika Anda memiliki sesuatu yang berfungsi untuk misalnya webpack, saya dapat melihat memolesnya.

Demikian pula, kita perlu mengintegrasikannya dengan pengalaman "kotak kesalahan", mirip dengan react-error-overlay kita miliki di Create React App. Itu memiliki beberapa nuansa, seperti kotak kesalahan akan hilang saat Anda memperbaiki kesalahan. Itu juga membutuhkan beberapa pekerjaan lebih lanjut yang dapat kita lakukan setelah yayasan terpasang.

Beri tahu saya jika Anda memiliki pertanyaan!

Semua 85 komentar

Ada kebingungan. DevTools baru tidak mengaktifkan hot reload (atau ada hubungannya dengan reload). Sebaliknya, perubahan hot reload Dan telah bekerja menggunakan "hook" yang digunakan DevTools dan React untuk berkomunikasi. Itu menambahkan dirinya sendiri ke tengah sehingga bisa melakukan reload.

Saya telah mengedit judul untuk menghapus penyebutan DevTools (karena dapat membingungkan).

Adapun pertanyaan tentang bagaimana HMR baru harus digunakan, saya rasa saya tidak tahu pemikiran terbaru di sana. Saya melihat @gaearon memiliki PR
https://github.com/facebook/create-react-app/pull/5958

Adapun pertanyaan tentang bagaimana HMR baru harus digunakan, saya rasa saya tidak tahu pemikiran terbaru di sana. Saya melihat @gaearon memiliki PR

Untuk memperjelas bagi pembaca, bahwa PR sudah sangat ketinggalan zaman dan tidak relevan lagi.


Saya perlu menuliskan sesuatu tentang cara kerja Fast Refresh dan cara mengintegrasikannya. Belum punya waktu.

Oke, ini dia.

Apa itu Fast Refresh?

Ini adalah implementasi ulang "hot reloading" dengan dukungan penuh dari React. Ini awalnya dikirim untuk React Native tetapi sebagian besar implementasinya tidak bergantung pada platform. Rencananya adalah untuk menggunakannya di seluruh papan - sebagai pengganti solusi murni userland (seperti react-hot-loader ).

Dapatkah Saya Menggunakan Penyegaran Cepat di Web?

Secara teoritis, ya, itulah rencananya. Praktisnya, seseorang perlu mengintegrasikannya dengan bundler yang umum di web (mis. Webpack, Parcel). Saya belum sempat melakukan itu. Mungkin seseorang ingin mengambilnya. Komentar ini adalah panduan kasar bagaimana Anda akan melakukannya.

Terdiri Dari Apa?

Fast Refresh mengandalkan beberapa bagian yang bekerja bersama:

  • Mekanisme "penggantian modul panas" dalam sistem modul.

    • Itu biasanya juga disediakan oleh bundler.

    • Misalnya di webpack, module.hot API memungkinkan Anda melakukan ini.

  • React renderer 16.9.0+ (mis. React DOM 16.9)
  • react-refresh/runtime titik masuk
  • react-refresh/babel Plugin Babel

Anda mungkin ingin mengerjakan bagian integrasi. Yaitu mengintegrasikan react-refresh/runtime dengan mekanisme "penggantian modul panas" Webpack.

Seperti Apa Integrasi Itu?

⚠️⚠️⚠️ JELAS, INI ADALAH PANDUAN BAGI ORANG YANG INGIN MELAKSANAKAN INTEGRASI DIRI. LANJUTKAN DENGAN HATI-HATI ANDA SENDIRI!

Ada beberapa hal yang ingin Anda lakukan secara minimal:

  • Aktifkan HMR di bundler Anda (mis. Webpack)
  • Pastikan React adalah 16.9.0+
  • Tambahkan react-refresh/babel ke plugin Babel Anda

Pada saat itu aplikasi Anda akan mogok. Ini harus berisi panggilan ke fungsi $RefreshReg$ dan $RefreshSig$ yang tidak ditentukan.

Kemudian Anda perlu membuat titik masuk JS baru yang harus dijalankan sebelum kode apa pun di aplikasi Anda , termasuk react-dom (!) Ini penting; jika berjalan setelah react-dom , tidak ada yang akan berhasil. Titik masuk itu harus melakukan sesuatu seperti ini:

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

Ini harus memperbaiki crash. Tetapi masih tidak akan melakukan apa-apa karena implementasi $RefreshReg$ dan $RefreshSig$ adalah noops. Menghubungkan mereka adalah inti dari pekerjaan integrasi yang perlu Anda lakukan.

Bagaimana Anda melakukannya tergantung pada bundler Anda. Saya kira dengan webpack Anda bisa menulis sebuah loader yang menambahkan beberapa kode sebelum dan setelah setiap modul dijalankan. Atau mungkin ada pengait untuk memasukkan sesuatu ke dalam template modul. Terlepas dari itu, apa yang ingin Anda capai adalah setiap modul terlihat seperti ini:

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

Idenya di sini adalah bahwa plugin Babel kami memancarkan panggilan ke fungsi ini, dan kemudian integrasi kami di atas mengikat panggilan tersebut ke ID modul. Sehingga runtime menerima string seperti "path/to/Button.js Button" ketika sebuah komponen sedang didaftarkan. (Atau, dalam kasus webpack, ID adalah angka.) Jangan lupa bahwa transformasi Babel dan penggabungan ini hanya boleh terjadi dalam mode pengembangan.

Sebagai alternatif untuk membungkus kode modul, mungkin ada beberapa cara untuk menambahkan percobaan / akhirnya seperti ini di sekitar tempat bundler benar-benar menginisialisasi pabrik modul. Seperti yang kami lakukan di sini di Metro (RN bundler). Ini mungkin akan lebih baik karena kita tidak perlu membengkak setiap modul, atau khawatir tentang memperkenalkan sintaks ilegal, misalnya saat membungkus import dengan try / finally .

Setelah Anda menghubungkannya, Anda memiliki satu masalah terakhir. Pemaket Anda tidak tahu bahwa Anda menangani pembaruan, jadi mungkin tetap memuat ulang halaman. Anda harus mengatakan untuk tidak melakukannya. Ini lagi-lagi khusus untuk bundler, tetapi pendekatan yang saya sarankan adalah memeriksa apakah semua ekspor adalah komponen React , dan dalam hal ini, "terima" pembaruan. Di webpack, ini bisa terlihat seperti:


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

Apakah isReactRefreshBoundary ? Ini adalah hal yang menghitung ekspor secara dangkal dan menentukan apakah hanya mengekspor komponen React. Begitulah cara Anda memutuskan apakah akan menerima pembaruan atau tidak. Saya tidak menyalinnya di sini tetapi penerapan ini bisa menjadi awal yang baik. (Dalam kode itu, Refresh mengacu pada react-refresh/runtime ekspor).

Anda juga ingin mendaftarkan semua ekspor secara manual karena Babel transform hanya akan memanggil $RefreshReg$ untuk fungsi. Jika Anda tidak melakukannya, pembaruan kelas tidak akan terdeteksi.

Terakhir, fungsi enqueueUpdate() akan menjadi sesuatu yang dibagi antara modul yang melakukan debounce dan melakukan update React yang sebenarnya.

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

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

Pada titik ini, Anda sudah memiliki sesuatu yang berfungsi.

Nuansa

Ada beberapa ekspektasi pengalaman dasar yang saya pedulikan yang mengarah ke branding "Penyegaran Cepat". Itu harus dapat pulih dengan baik dari kesalahan sintaks, kesalahan inisialisasi modul, atau kesalahan rendering. Saya tidak akan membahas mekanisme ini secara mendetail, tetapi saya sangat yakin Anda tidak boleh menyebut eksperimen Anda "Penyegaran Cepat" hingga eksperimen tersebut menangani kasus tersebut dengan baik.

Sayangnya, saya tidak tahu apakah webpack dapat mendukung semua itu, tetapi kami dapat meminta bantuan jika Anda mendapatkan status yang agak berfungsi tetapi kemudian macet. Misalnya, saya perhatikan bahwa API accept() webpack membuat pemulihan kesalahan lebih sulit (Anda perlu menerima versi modul _previous_ setelah kesalahan), tetapi ada cara untuk meretasnya. Hal lain yang perlu kita bahas adalah secara otomatis "mendaftarkan" semua ekspor, dan bukan hanya yang ditemukan oleh plugin Babel. Untuk saat ini, mari abaikan ini, tetapi jika Anda memiliki sesuatu yang berfungsi untuk misalnya webpack, saya dapat melihat memolesnya.

Demikian pula, kita perlu mengintegrasikannya dengan pengalaman "kotak kesalahan", mirip dengan react-error-overlay kita miliki di Create React App. Itu memiliki beberapa nuansa, seperti kotak kesalahan akan hilang saat Anda memperbaiki kesalahan. Itu juga membutuhkan beberapa pekerjaan lebih lanjut yang dapat kita lakukan setelah yayasan terpasang.

Beri tahu saya jika Anda memiliki pertanyaan!

Kesalahan sintaks / kesalahan inisialisasi harus "cukup mudah" untuk ditangani sebelum memberi tahu React untuk memulai render, tetapi bagaimana kesalahan rendering berinteraksi dengan batasan kesalahan?

Jika terjadi kesalahan rendering, itu akan memicu batas kesalahan terdekat yang akan memotret dirinya sendiri ke dalam keadaan kesalahan, dan tidak ada cara umum untuk memberi tahu batas kesalahan bahwa anak-anak mereka secara ajaib _possibly_ diperbaiki setelah penyegaran langsung. Apakah / haruskah setiap komponen yang dapat di-refresh mendapatkan batas errornya sendiri secara gratis, atau apakah penanganan error bekerja secara berbeda di reconciler ketika dukungan runtime terdeteksi saat inisialisasi?

semua ekspor adalah komponen React, dan dalam hal ini, "terima" pembaruan

Adakah cara untuk mendeteksi komponen semacam itu? Sejauh yang saya mengerti - tidak. Kecuali export.toString().indexOf('React')>0 , tetapi akan berhenti bekerja dengan HOC yang diterapkan.
Ditambah _self accepting_ di akhir file tidak rawan error - accept handle yang baru tidak akan dibuat, dan update berikutnya akan mengarah ke batas yang lebih tinggi, itulah mengapa require("react-hot-loader/root").hot dibuat.

Bagaimanapun - tampaknya jika seseorang akan membuang semua kode spesifik-react dari react-hot-loader , menjaga API eksternal tidak tersentuh - itu sudah cukup, dan berlaku untuk semua instalasi yang ada.

Menggunakan react-refresh/babel 0.4.0 memberi saya kesalahan ini pada sejumlah besar file:

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)

Saya mempersempit file itu menjadi hal paling sederhana yang menyebabkannya:

import { useContext } from 'react'

export default () => useContext()

Jika terjadi kesalahan rendering, itu akan memicu batas kesalahan terdekat yang akan memotret dirinya sendiri ke dalam keadaan kesalahan, dan tidak ada cara umum untuk memberi tahu batas kesalahan bahwa anak-anak mereka secara ajaib mungkin diperbaiki setelah penyegaran langsung.

Kode Refresh Cepat di dalam React mengingat batasan mana yang saat ini gagal. Setiap kali pembaruan Fast Refresh dijadwalkan, pembaruan itu akan selalu dipasang ulang.

Jika tidak ada batasan, tetapi root gagal saat update, Fast Refresh akan mencoba merender root tersebut dengan elemen terakhirnya.

Jika root gagal saat mount, runtime.hasUnrecoverableErrors() akan memberitahu Anda. Maka Anda harus memaksa memuat ulang. Kasus itu bisa kami tangani nanti, saya belum punya waktu untuk memperbaikinya.

Menggunakan react-refresh / babel 0.4.0 memberi saya kesalahan ini pada sejumlah besar file:

Tolong ajukan masalah baru?

Adakah cara untuk mendeteksi komponen semacam itu?

Saya menautkan ke implementasi saya, yang dengan sendirinya menggunakan Runtime.isLikelyAReactComponent() . Itu tidak sempurna tapi cukup bagus.

pegangan terima yang baru tidak akan dibuat, dan pembaruan berikutnya akan mengarah ke batas yang lebih tinggi

Bisakah Anda membuat contoh? Saya tidak mengikuti. Terlepas dari itu, itu adalah sesuatu yang spesifik untuk bundler. Saya membuat Metro melakukan apa yang saya inginkan. Kami dapat meminta webpack untuk menambahkan sesuatu jika kami kehilangan API.

Tujuannya di sini adalah untuk menjalankan kembali sesedikit mungkin modul sambil menjamin konsistensi. Kami tidak ingin membuat gelembung pembaruan ke root untuk sebagian besar pengeditan.

tampaknya jika seseorang akan membuang semua kode react-spesifik dari react-hot-loader, menjaga API eksternal tidak tersentuh

Mungkin, meskipun saya juga ingin menghapus penampung tingkat atas. Saya juga ingin integrasi yang lebih erat dengan kotak kesalahan. Mungkin itu masih bisa disebut react-hot-loader .

Ngomong-ngomong, saya mengedit panduan saya untuk menyertakan bagian yang hilang yang saya lupa - panggilan performReactRefresh . Itulah hal yang sebenarnya menjadwalkan pembaruan.

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

Saya tidak akan merasa aman dengan logika seperti itu. Bahkan jika semua CapitalizedFunctions adalah komponen React hampir selalu - banyak modul (milik saya) memiliki ekspor lain juga. Misalnya _exports-for-tests_. Itu bukan masalah, tetapi menciptakan beberapa _unpredictability_ - batas panas dapat dibuat kapan saja ... atau tidak dibuat setelah satu baris berubah.
Apa yang bisa mematahkan tes isLikelyComponentType :

  • mengekspor mapStateToProps (untuk pengujian, tidak digunakan dalam kode produksi)
  • mengekspor hook (dan tidak apa-apa)
  • mengekspor Class yang mungkin bukan kelas react (tidak mau, tapi seharusnya)

Jadi - akan ada kasus ketika batas panas akan ditetapkan, tetapi tidak akan, dan akan ada kasus ketika batas panas akan ditetapkan, tetapi tidak akan. Kedengarannya seperti hot-reload lama yang tidak stabil yang kami berdua tidak suka :)

Ada satu tempat di mana menerapkan batas panas tidak akan begitu tidak terduga, dan akan sangat diharapkan - thing atau domain batas, atau indeks direktori, yaitu index.js reexporting sebuah "API publik" dari Component.js di direktori yang sama (bukan gaya Facebook afaik).

Dengan kata lain - semua seperti yang Anda lakukan di Metro, tetapi dengan lebih banyak batasan yang diterapkan. Yang lainnya, seperti aturan linting agar batas tersebut ditetapkan untuk setiap komponen yang dimuat lambat, dapat digunakan untuk memberlakukan perilaku yang benar.

Ngomong-ngomong - hot fast refresh akan menangani Malas? Apakah diharapkan ada batas dari sisi lain import ?

Coba cepat lihat keajaiban di browser dan itu sangat bagus :) Saya melakukan hal yang paling sederhana, yaitu melakukan hardcode semua kode instrumentasi, jadi belum ada plugin webpack di sana

Kapture 2019-09-07 at 23 09 04

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

Hanya ingin tahu tetapi untuk webpack, tidak bisakah Anda memiliki plugin babel untuk menyelesaikan percobaan / akhirnya? Hanya ingin memastikan saya tidak melewatkan sesuatu sebelum mencobanya.

Plugin Babel tidak khusus lingkungan. Saya ingin tetap seperti itu. Ia tidak tahu apa-apa tentang modul atau mekanisme perbaruan propagasi. Itu berbeda tergantung pada bundlernya.

Misalnya di Metro tidak ada try / last wrapping transform sama sekali. Sebagai gantinya saya mencoba / akhirnya di runtime bundler itu sendiri di sekitar tempat itu memanggil pabrik modul. Itu akan ideal dengan webpack juga, tetapi saya tidak tahu apakah itu memungkinkan Anda menghubungkan ke runtime seperti itu.

Anda tentu saja dapat membuat plugin Babel lain untuk pembungkus. Tapi itu tidak membeli apa-apa selain melakukan itu melalui webpack. Karena ini khusus webpack. Dan bisa membingungkan bahwa Anda bisa secara tidak sengaja menjalankan plugin Babel di lingkungan lain (bukan webpack) yang tidak masuk akal.

Anda bisa, dengan mengaitkan kail waterfall compilation.mainTemplate.hooks.require . Permintaan sebelumnya adalah badan default dari fungsi __webpack_require__ , sehingga Anda dapat menggunakan hook untuk membungkus isinya menjadi blok try/finally .

Masalahnya adalah mendapatkan referensi ke React di dalam __webpack_require__ . Itu mungkin, tetapi mungkin memerlukan beberapa tingkat reentrancy dan penjaga rekursi.

Untuk detail selengkapnya, periksa MainTemplate.js dan web/JsonpMainTemplatePlugin.js di kode sumber webpack. JsonpMainTemplatePlugin itu sendiri hanya menyentuh seikat kait dari MainTemplate.js jadi itu mungkin "daging" yang perlu Anda tangani.

Ini adalah prototipe bodoh yang saya retas bersama yang melakukan secara efektif apa yang dijelaskan Dan di atas. Ini sangat tidak lengkap, tetapi membuktikan implementasi lo-fi di webpack: https://gist.github.com/maisano/441a4bc6b2954205803d68deac04a716

Beberapa catatan:

  • react-dom -hardcode di sini, jadi ini tidak akan bekerja dengan perender atau sub-paket kustom (misalnya react-dom/profiling ).
  • Saya belum melihat terlalu dalam tentang cara kerja semua varian template webpack, tetapi cara saya membungkus eksekusi modul cukup hacky. Saya tidak yakin apakah contoh ini akan berhasil jika, katakanlah, seseorang menggunakan target library umd .

Masalahnya adalah mendapatkan referensi ke React di dalam __webpack_require__. Itu mungkin, tetapi mungkin memerlukan beberapa tingkat reentrancy dan penjaga rekursi.

Saya berasumsi maksud Anda mendapatkan referensi ke Refresh Runtime.

Di Metro saya telah memecahkan masalah ini dengan melakukan require.Refresh = RefreshRuntime sedini mungkin. Kemudian di dalam implementasi require saya dapat membaca properti dari fungsi require itu sendiri. Ini tidak akan segera tersedia tetapi tidak masalah jika kita mengaturnya cukup awal.

@maisano Saya harus mengubah beberapa hal, dan akhirnya saya tidak melihat fungsi .accept yang dipanggil oleh webpack. Saya sudah mencoba .accept(module.i, () => {}) dan .accept(() => {}) (menerima sendiri, kecuali ini tidak berfungsi di webpack). Properti hot diaktifkan, saya melihatnya turun dan dijalankan melalui modul yang diterima.

Jadi saya akhirnya menambal webpack untuk memanggil modul yang dapat menerima sendiri, dan itu adalah perbaikan terakhir.

Berikut patchnya:

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

Saya tahu ini bertentangan dengan API mereka, yang menginginkannya menjadi "errorCallback", saya ingat pernah mengalami ini secara khusus bertahun-tahun yang lalu saat mengerjakan HMR internal kami, dan akhirnya kami akhirnya menulis bundler kami sendiri. Saya yakin parcel mendukung API panggilan balik "menerima sendiri". Mungkin ada baiknya kita membuka masalah di webpack dan melihat apakah kita bisa menggabungkannya? @kokra

Jadi ... Saya lebih lanjut memoles plugin berdasarkan karya @maisano :
https://github.com/pmmmwh/react-refresh-webpack-plugin
(Saya menulisnya di TypeScript karena saya tidak percaya diri saya mengutak-atik internal webpack ketika saya mulai, saya dapat mengonversinya menjadi JS / Flow biasa)

Saya mencoba menghilangkan kebutuhan loader untuk menyuntikkan kode modul panas dengan webpack Dependency kelas, tetapi tampaknya itu akan membutuhkan parsing ulang semua modul (karena bahkan dengan semua fungsi sebaris, kita masih membutuhkan referensi ke react-refresh/runtime di suatu tempat).

Masalah lainnya adalah tidak ada cara sederhana (afaik) untuk mendeteksi file seperti JavaScript di webpack - misalnya html-webpack-plugin menggunakan tipe javascript/auto juga, jadi saya melakukan hard-code apa yang tampaknya masker file yang dapat diterima (JS / TS / Flow) untuk injeksi loader.

Saya juga menambahkan pemulihan kesalahan (setidaknya kesalahan sintaks) berdasarkan komentar dari @gaearon di utas berusia 5 tahun ini . Selanjutnya adalah memulihkan dari kesalahan reaksi - Saya curiga ini dapat dilakukan dengan memasukkan batas kesalahan global (agak seperti AppWrapper dari react-hot-loader ), yang juga akan menangani antarmuka kotak kesalahan, tetapi tidak punya waktu untuk itu dulu.

Masalah yang diangkat oleh @natew juga dihindari - dicapai dengan memisahkan panggilan enqueueUpdate dan panggilan hot.accpet(errorHandler) .

@pmmmwh Waktunya apa! Saya baru saja membuat repo yang dibangun di / tweak sedikit dari pekerjaan yang telah saya bagikan di intinya.

Saya belum pernah menangani kesalahan dalam hal apa pun, meskipun plugin di sini sedikit lebih solid daripada pendekatan awal yang saya ambil.

Selanjutnya adalah memulihkan dari kesalahan reaksi - Saya menduga ini dapat dilakukan dengan memasukkan batas kesalahan global (seperti AppWrapper dari react-hot-loader), yang juga akan menangani antarmuka kotak kesalahan, tetapi tidak punya waktu untuk melakukannya itu dulu.

Itu seharusnya sudah berhasil di luar kotak. Tidak perlu batas atau penggabungan kesalahan khusus di sini.

Selanjutnya adalah memulihkan dari kesalahan reaksi - Saya menduga ini dapat dilakukan dengan memasukkan batas kesalahan global (seperti AppWrapper dari react-hot-loader), yang juga akan menangani antarmuka kotak kesalahan, tetapi tidak punya waktu untuk melakukannya itu dulu.

Itu seharusnya sudah berhasil di luar kotak. Tidak perlu batas atau penggabungan kesalahan khusus di sini.

@gaearon Aneh. Saya mencoba melempar kesalahan dalam merender komponen fungsi - jika kesalahan terjadi pada return , HMR berfungsi, tetapi jika terjadi di tempat lain, terkadang tidak berfungsi.

@pmmmwh Waktunya apa! Saya baru saja membuat repo yang dibangun di / tweak sedikit dari pekerjaan yang telah saya bagikan di intinya.

Saya belum pernah menangani kesalahan dalam hal apa pun, meskipun plugin di sini sedikit lebih solid daripada pendekatan awal yang saya ambil.

@maisano Apa yang harus saya katakan? Saya sebenarnya mulai mengerjakan ini dan terjebak dengan masalah injeksi ketergantungan akhir pekan lalu ... Inti Anda memberi saya jalan keluar: tada:

jika kesalahan terjadi sebagai balasannya, HMR berfungsi, tetapi jika terjadi di tempat lain, terkadang tidak berfungsi.

Saya perlu detail lebih lanjut tentang apa yang sebenarnya Anda coba dan apa yang Anda maksud dengan "berhasil" dan "tidak berhasil".

Ada banyak hal yang bisa salah jika integrasi bundler modul tidak diterapkan dengan benar (yang merupakan topik atau utas ini). Saya berharap bahwa tidak ada di React itu sendiri yang mencegah pemulihan dari kesalahan yang diperkenalkan selama pengeditan. Anda dapat memverifikasi bahwa itu berfungsi di React Native 0.61 RC3.

@pmmmwh , @maisano pemeriksaan berikut melompati modul dengan komponen sebagai ekspor bernama dan tidak ada batas penyegaran yang dibuat:

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

Saya tidak tahu mengapa ini diperlukan di Metro , tetapi di webpack getter hanya mengembalikan ekspor bernama dan sejauh yang saya tahu tidak ada efek samping. Jadi harus aman untuk dilepas.

@gaearon React.lazy komponen (misalnya rute pembagian kode) tidak dirender ulang. Apakah itu desainnya? Saya dapat melihat bahwa batas penyegaran telah ditetapkan tetapi performReactRefresh() tampaknya tidak melakukan apa-apa. Perubahan pada anak malas menyegarkan baik-baik saja, jadi ini bukan masalah besar, tapi saya bertanya-tanya apakah kita melakukan sesuatu yang salah ...

lazy adalah mesin status kecil - ini menyimpan referensi ke komponen lama, dan referensi itu harus diperbarui.
Sekarang mari kita bayangkan itu, dan sekarang mengacu pada objek lazy ) - itu harus memikirkan fase loading lagi, dan itu mungkin akan menghancurkan semua pohon bersarang.

Saya berharap malas bekerja. Mungkin ada yang rusak. Saya perlu melihat kasus reproduksi.

Karena ada beberapa prototipe, haruskah kita memilih satu dan kemudian memindahkan diskusi ini ke masalahnya? Dan lakukan iterasi di sana.

Ada:
https://github.com/maisano/react-refresh-plugin
dan:
https://github.com/pmmmwh/react-refresh-webpack-plugin
Saya telah menyiapkan garpu plugin pmmmwh yang berfungsi dengan [email protected] (juga memperbaiki ekspor bernama):
https://github.com/WebHotelier/webpack-fast-refresh

Bagaimana dengan react-hot-loader ?

react-hot-loader mem-backport hampir semua fitur dari fast refresh , tetapi ada beberapa momen historis dan integral, yang tidak membiarkan semua backport, dan, sejujurnya, tidak ada gunanya menerapkannya kembali dalam istilah "rhl" . Jadi - biarkan pensiun.

Saya perlu detail lebih lanjut tentang apa yang sebenarnya Anda coba dan apa yang Anda maksud dengan "berhasil" dan "tidak berhasil".

Ada banyak hal yang bisa salah jika integrasi bundler modul tidak diterapkan dengan benar (yang merupakan topik atau utas ini). Saya berharap bahwa tidak ada di React itu sendiri yang mencegah pemulihan dari kesalahan yang diperkenalkan selama pengeditan. Anda dapat memverifikasi bahwa itu berfungsi di React Native 0.61 RC3.

Setelah beberapa penyesuaian, saya dapat memverifikasi bahwa itu berfungsi.

Namun - tampaknya plugin babel tidak berfungsi untuk kelas. Saya telah memeriksa dan ini tampaknya terjadi tanpa memperhatikan implementasi, karena semua kode yang disuntikkan dan react-refresh/runtime berfungsi dengan baik. Saya tidak yakin apakah ini dimaksudkan atau apakah ini khusus webpack, jika yang terakhir saya dapat mencoba untuk memperbaiki besok. (Saya juga menguji ini hanya dengan preset metro, mereproduksi intinya di sini )

Saya agak yakin bahwa ini berfungsi untuk RN, tetapi pada mesin saya saat ini, saya tidak memiliki lingkungan yang berguna untuk diuji di RN jadi jika Anda dapat mengarahkan saya ke implementasi plugin babel di metro atau transformasi itu akan sangat membantu.

Karena ada beberapa prototipe, haruskah kita memilih satu dan kemudian memindahkan diskusi ini ke masalahnya? Dan lakukan iterasi di sana.

Mungkin kita bisa pergi ke sini ? Sejak komentar terakhir saya, saya telah mem-porting seluruh proyek menjadi JS biasa dan menambahkan beberapa perbaikan pada antrian pembaruan. Saya tidak perlu mem-port plugin untuk webpack @ 5 , tetapi setelah membaca fork oleh @apostolos dan logika HMR baru di webpack @ next , perbaikannya harus langsung ke depan.

Ya, plugin Babel tidak akan mendaftarkan kelas. Tujuannya adalah agar ini terjadi pada tingkat sistem modul. Setiap ekspor harus diperiksa apakah "kemungkinan" merupakan komponen React. (Fungsi pemeriksaan disediakan oleh runtime.) Jika benar, daftarkan ekspor, seperti yang dilakukan plugin Babel. Beri ID seperti filename exports%export_name . Inilah yang membuat kelas berfungsi di Metro, karena plugin Babel tidak akan menemukannya.

Dengan kata lain, karena kita tidak dapat mempertahankan status kelas, kita mungkin juga mempercayakan "menempatkan" mereka ke batas ekspor modul daripada mencoba menemukannya sebaris dalam kode sumber dengan transformasi. Ekspor harus bertindak sebagai "penampung semua" untuk komponen yang tidak kami temukan dengan plugin.

Mailchimp mulai menggunakan garpu dari plugin yang terakhir saya bagikan. Ini telah disempurnakan sedikit lebih banyak dan orang-orang yang telah memilih untuk menggunakannya tampaknya sangat senang. Kami akan terus mengulanginya secara lokal. Saya berencana untuk menghapus percabangan dan mempublikasikan pembaruan ke hulu setelah sedikit lebih jauh.

@gaearon Pemikiran tentang menambahkan Simbol kita dapat melampirkan hal-hal yang kita tahu aman untuk disegarkan, tetapi bukankah komponen? Misalnya kami memiliki pola seperti:

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

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

Dimana create hanya mengembalikan sebuah objek. Saya telah menambalnya untuk saat ini di pihak saya, tetapi kami dapat dengan mudah menambahkan simbol ke objek ekspor default di sana yang menunjukkan ini adalah file yang aman. Tidak yakin persis dengan pola terbaiknya.

Sunting: Saya menyadari ini bisa masuk ke dalam implementasi penyegaran! Saya pikir itu mungkin lebih baik dalam runtime tapi mungkin tidak. Dengan begitu banyak impl yang berbeda dari loader, mungkin lebih baik untuk memiliki cara standar.

Mari maju 10 tahun ke depan. Seperti apa basis kode Anda? Izinkan di sini, larang di sana? Bagaimana cara agar bendera ini tetap mutakhir? Bagaimana alasannya? Seperti ada _safe untuk memperbarui_ lokasi, dan _unsafe_, Anda harus mempertahankan, atau tidak dapat merekonsiliasi dengan benar karena beberapa alasan. Alasan manakah dalam setiap kasus yang merupakan alasan yang valid?

  • yang symbols Anda akan memiliki lebih banyak - sekitar force allow memuat ulang, atau force disallow memuat ulang
  • mengapa Anda mungkin ingin menurunkan batas propagasi pembaruan (yaitu menerima pembaruan pada batas modul "ini"), atau ingin menaikkannya (yaitu menerima pembaruan pada batas modul "itu")
  • apa yang akan terjadi jika tidak ada batasan yang ditetapkan? Apakah ini hanya masalah kinerja, atau sesuatu yang lebih parah bisa terjadi?

Hai orang-orang 👋 Saya ingin membantu di sini. Sudahkah kita menyetujui satu repo / upaya?

Apakah repo ini dibagikan oleh @pmmmwh?
https://github.com/pmmmwh/react-refresh-webpack-plugin

Atau repo ini dibagikan oleh @maisano?
https://github.com/maisano/react-refresh-plugin

Sepertinya yang oleh @pmmmwh telah dilakukan baru-baru ini. Kecuali saya mendengar sebaliknya, saya akan berasumsi bahwa itulah yang harus difokuskan.

Implementasi di Parcel 2 telah dimulai di sini: https://github.com/parcel-bundler/parcel/pull/3654

Musim panas!

Bagi siapa pun yang mencarinya, implementasi React Refresh untuk proyek Rollup menggunakan Nollup untuk pengembangan: https://github.com/PepsRyuu/rollup-plugin-react-refresh

Mungkin bukan implementasi terbersih, tetapi berhasil.

Untuk solusi webpack, sepertinya belum ada rilis resmi dari plugin di atas, jadi sepertinya solusi HMR terbaik untuk bereaksi adalah library Dan disini: https://github.com/gaearon/react-hot-loader

Kami baru saja mengirimkan Parcel 2 alpha 3 dengan dukungan untuk Penyegaran Cepat di luar kotak! Jangan ragu untuk mencobanya. 😍 https://twitter.com/devongovett/status/1197187388985860096?s=20

🥳 menambahkan catatan penghentian ke RHL 🥳

Resep yang telah saya gunakan untuk mencoba ini di aplikasi CRA menggunakan pekerjaan @pmmmwh yang sedang berlangsung , react-app-rewired , dan 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

Edit ./package.json :

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

Tambahkan ./config-overrides.js file:

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

Menikmati pengalaman sejauh ini. Terima kasih untuk semua pekerjaan dari semua orang yang terlibat!

Terima kasih @ drather19 !

Saya membuat repositori berdasarkan instruksi Anda, berfungsi:https://github.com/jihchi/react-app-rewired-react-refreshJika seseorang ingin mencobanya dan menyimpan beberapa pengetikan, silakan kloning repo.


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

DAN ... v0.1.0 untuk Webpack baru saja dikirim 🎉

@ dratnawijaya2
Kalian mungkin ingin beralih ke versi itu - ini mencakup pengalaman yang lebih terpadu serta banyak perbaikan bug pada implementasi awal.

@pmmmwh mendukung ts-loader + babel-loader ?

@pmmmwh mendukung ts-loader + babel-loader ?

Saya melakukan pengujian terhadap TS hanya dengan Babel dan berhasil, jadi jika tidak berhasil saat Anda menggunakan pemuat ts + babel jangan ragu untuk mengajukan masalah :)

@ drather19 Saya mencoba mengkloning dan menjalankan repo Anda, tetapi server dev tidak pernah dijalankan.

Lingkungan Hidup,
OS - OSX 10.14.6
Node - v12.13.0
Benang -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 Saya mencoba mengkloning dan menjalankan repo Anda, tetapi server dev tidak pernah dijalankan.

Lingkungan Hidup,
OS - OSX 10.14.6
Node - v12.13.0
Benang -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...

Ini telah diperbaiki di cabang master plugin, dan akan dirilis besok.

Saya berhasil mendapatkan @pmmmwh 's Webpack Plugin bekerja dengan naskah aplikasi Bereaksi menggunakan babel. Namun, build inkremental membutuhkan waktu sekitar 12 detik, bukan ~ 2 detik hanya dengan ts-loader. Saya akan terus bermain dengan ini untuk melihat apakah saya melewatkan sesuatu di sisi konfigurasi babel yang membuat kinerjanya lebih dekat, tetapi untuk saat ini ini adalah pencucian dibandingkan dengan ts-loader dan penyegaran penuh.

@IronSean Tolong laporkan di repo plugin itu? Ini tidak terdengar normal.

Saya akan terus bermain dengan ini untuk melihat apakah saya melewatkan sesuatu di sisi konfigurasi babel yang membuat kinerjanya lebih dekat, tetapi untuk saat ini ini adalah pencucian dibandingkan dengan ts-loader dan penyegaran penuh.

Keberatan memposting konfigurasi / pengaturan Anda di sana? Saya tidak akan dapat memecahkan masalah tanpa konteks lebih lanjut.

@pmmmwh Saya membuka masalah ini untuk memindahkan diskusi ke repo Anda setelah saya mengonfirmasi bahwa memang plugin Anda yang membuat perbedaan:
https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/20

Akankah react-refresh ( React Fast Refresh ?) Berfungsi dengan Preact, atau apakah react-hot-loader solusi jangka panjang untuk Preact?

@Jumblemuddle yang bergantung pada Preact tetapi mereka harus dapat berintegrasi dengan Fast Refresh jika mereka mau.

Untuk orang-orang CRA yang ingin menjalankan Fast Refresh, saya lebih beruntung dengan craco (vs. react-app-rewired + customize-cra) sekarang melalui 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 })],
        [],
      ),
    ],
  },
};

Secara khusus, menambahkan webpackConfig.optimization.runtimeChunk = false; akan memungkinkan Anda menambah / menghapus kait dan masih menyegarkan dengan cepat.

Lebih menikmati pengalaman yang ditingkatkan sekarang. Terima kasih kepada @ mmhand123 untuk tipnya melalui https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/25! (<- terselesaikan!)

Berdasarkan saran oleh @ drather19 saya telah menerbitkan plugin kustomisasi-cra agar lebih mudah. Lihat esetnik / customize-cra-react-refresh .

Terima kasih kepada @ drather19 saya sedikit memodifikasi kode sekarang ini dapat bekerja dalam pengaturan monorepo ruang kerja benang.

Pertama, instal yang berikut ini di sub-paket yang ingin Anda aktifkan refresh cepat:

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

Kemudian tambahkan ini ke 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 Apakah kami mengharapkan Fast Refresh tersedia di CRA secara default pada suatu saat?
jika demikian apa yang dibutuhkan untuk itu?

Sejumlah pekerjaan diperlukan untuk itu :-) yang saat ini sedang dilakukan.

jika menggunakan fungsi HMR akan dipanggil? misalnya componentDidMount.
Saya menggunakan react-proxy dan componentDidMount akan dipanggil.
Dan bereaksi 15.X dapat menggunakan Fast Refresh?

  • componentDidMount akan dipanggil. Serta unmount - kelas akan dimuat ulang secara penuh.
  • dan inilah saat yang tepat untuk berhenti menggunakan react-proxy . Nah, Anda mungkin harus berhenti beberapa tahun yang lalu.
  • 15.X ? - benar-benar tidak. Fast Refresh __adalah bagian__ dari react, dan dengan demikian hanya ada dalam versi modern.

jadi kita harus menggunakan Fast Refresh atau react-hot-loader untuk menggantikan react-proxy?
Apakah ada cara untuk mencegah fungsi (componentDidMount) mengeksekusi HMR? - Ini akan memanggil metode untuk mendapatkan data baru.

Bagaimana cara menggunakan react-hot-loader di JIT? - Kompilasi waktu nyata peramban

  • jadi kita harus menggunakan Fast Refresh atau react-hot-loader untuk menggantikan react-proxy?

    Coba fast refresh dulu, lalu RHL

  • Apakah ada cara untuk mencegah fungsi (componentDidMount) mengeksekusi HMR? - Ini akan memanggil metode untuk mendapatkan data baru.

    (gunakan kait ...), jangan bergantung pada siklus hidup komponen, ambil data saat diperlukan. Coba react-query , swr atau solusi lainnya.

Adapun pertanyaan tentang bagaimana HMR baru harus digunakan, saya rasa saya tidak tahu pemikiran terbaru di sana. Saya melihat @gaearon memiliki PR

Untuk memperjelas bagi pembaca, bahwa PR sudah sangat ketinggalan zaman dan tidak relevan lagi.

Saya perlu menuliskan sesuatu tentang cara kerja Fast Refresh dan cara mengintegrasikannya. Belum punya waktu.

Sampai saat ini, Humas tersebut masih buka. Alangkah baiknya jika hanya PR relevan yang masih memiliki kesempatan untuk digabungkan akan tetap terbuka untuk mendapatkan gambaran yang lebih baik. Jika Anda hanya menyimpannya sebagai referensi, saya akan merekomendasikan memindahkan barang ke cabang, tag atau repositori yang berbeda.

Saya terus mendapatkan Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly. Saya telah mengikuti dokumentasi tetapi sepertinya saya melewatkan sesuatu?

Saya terus mendapatkan Kesalahan: [React Refresh] Hot Module Replacement (HMR) tidak diaktifkan! React Refresh membutuhkan HMR agar berfungsi dengan baik. Saya telah mengikuti dokumentasinya tetapi sepertinya saya melewatkan sesuatu?

@silkfire Saya berasumsi Anda menggunakan plugin webpack. Jika ya, harap ajukan pertanyaan Anda di repo plugin webpack: https://github.com/pmmmwh/react-refresh-webpack-plugin/.

Sampai saat ini, Humas tersebut masih buka. Alangkah baiknya jika hanya PR relevan yang masih memiliki kesempatan untuk digabungkan akan tetap terbuka untuk mendapatkan gambaran yang lebih baik. Jika Anda hanya menyimpannya sebagai referensi, saya akan merekomendasikan memindahkan barang ke cabang, tag atau repositori yang berbeda.

Saya menghargai saran Anda, tetapi dengan ribuan pemberitahuan yang belum dibaca terkadang sulit bagi saya untuk mengingat kembali PR lama. Saya percaya pengelola repositori Create React App untuk melakukan hal yang benar dan menutup jika mereka menganggapnya tidak berguna lagi.

Saya akan menutup ini.

Kami memiliki https://github.com/pmmmwh/react-refresh-webpack-plugin/ sebagai implementasi referensi untuk webpack.
Dan https://github.com/facebook/react/issues/16604#issuecomment -528663101 menjelaskan cara membuat integrasi ubahsuaian.

Saya terus mendapatkan Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly. Saya telah mengikuti dokumentasi tetapi sepertinya saya melewatkan sesuatu?

Sepertinya Anda belum mengaktifkan HMR webpack. Untuk bantuan lebih lanjut, silakan ajukan masalah di repo plugin.

Karena Hot Replacement sekarang menjadi bagian dari React - jika itu memiliki tempat terpisah dalam dokumentasi React, menunjuk ke perpustakaan tambahan untuk digunakan dengan bundler dan platform tertentu, serta menjelaskan beberapa gotcha yang masih ada, seperti dengan css yang memperbarui sendiri modul.

Informasi seperti ini tidak boleh terkubur dalam masalah github dan posting blog.

@theKashey ada di React, tetapi implementasi react-dom hanya eksperimental, untuk satu.
Selain itu, ada implementasi penyegaran cepat yang akan digabungkan dengan create-react-app, tetapi belum dirilis: pmmmwh / react-refresh-webpack-plugin # 7. Mungkin akan ada di versi react-scripts berikutnya.

Jadi mungkin tim React saat ini merasa kurang tepat untuk membicarakan Fast Refresh untuk react-dom dalam fase percobaan ini.

itu ada di React, tetapi implementasi react-dom hanya eksperimental, untuk satu.

Untuk lebih jelasnya, implementasi di react-dom itu sendiri stabil, seperti di React Native. Hanya saja integrasi tidak semuanya stabil.

haruskah itu memiliki tempat terpisah dalam dokumentasi React, menunjuk ke pustaka tambahan untuk digunakan dengan bundler dan platform tertentu, serta menjelaskan beberapa gotcha yang masih ada, seperti dengan modul css yang memperbarui sendiri.

Kedengarannya masuk akal. Saya akan senang mengambil PR menambahkannya ke bagian Panduan Lanjutan, mungkin berdasarkan halaman RN serupa .

@gaul
Aplikasi react saya baik-baik saja dengan beberapa perubahan komponen yang ditata dan menerapkan perubahan itu dengan benar tanpa masalah.
Namun, ketika saya mengubah beberapa kode di reducer Redux, seluruh aplikasi di-hard-refresh dan kehilangan semua status redux.
Apakah saya perlu menggunakan beberapa pustaka lain seperti redux-persist untuk menyimpan keadaan saat ini bersama dengan react-fast-refresh ?

Kami telah melakukan lingkaran penuh dan ini dia lagi 😅

Begitulah cara kerja HMR tingkat rendah, dan berada di luar tanggung jawab penyegaran cepat. Silakan merujuk ke redux atau dokumen webpack

Kami telah melakukan lingkaran penuh dan ini dia lagi 😅

Begitulah cara kerja HMR tingkat rendah, dan berada di luar tanggung jawab penyegaran cepat. Silakan merujuk ke redux atau dokumen webpack

Apakah Anda akan menautkan referensi lingkaran penuh?

@ jwchang0206 Pastikan Anda memiliki kode seperti ini di toko Anda.

Apakah Anda akan menautkan referensi lingkaran penuh?

Pertanyaan yang sama ditanyakan untuk React Hot Loader. Jawaban yang sama diberikan. Kami berada di awal siklus baru.

@ jwchang0206 Lihat -reducers-injector , perpustakaan kecil yang saya tulis untuk mengatasi masalah ini.
Ini akan memungkinkan Anda untuk mendukung reduksi memuat ulang dengan memuat ulang panas.
Pastikan Anda mengikuti prinsip reduks tentang kekekalan dalam reduksi Anda dan ini akan bekerja dengan lancar 💯
Dan jika Anda menggunakan saga, Anda dapat menggunakan redux-sagas-injector .

@gaearon Saya agak bingung dengan penggunaan window . Bagi saya tidak terlihat seolah-olah itu benar-benar perlu karena implementasinya ditukar? Apa gunanya itu?

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
}

Saya memiliki bundler khusus saya sendiri dan saya sedang dalam proses menerapkan ini, tetapi saya tidak dapat melihat mengapa itu menjadi keharusan mutlak atau apa gunanya itu ... awalnya saya curiga beberapa penggunaan memori / pengoptimalan kebocoran tetapi ini hanya panggilan yang diteruskan ke RefreshRuntime ...

@leidegre Saya tidak dapat mengomentari keputusan untuk menyetel $ RefreshSig $ pada objek jendela, tetapi penggabungan ke lingkungan browser memberi saya masalah saat mengonsumsi Fast Refresh di React NativeScript. @pmmmwh datang untuk menyelamatkan dengan mengadaptasi plugin Fast Refresh Webpack untuk mengatasi kopling Fast Refresh ke browser (masalah yang ditemukan dan diatasi dibahas di utas ini: https://github.com/pmmmwh/react-refresh-webpack-plugin/ masalah / 79). Saya ingin tahu apakah pendekatan yang digunakan akan berguna bagi Anda dalam integrasi Fast Refresh bundler kustom Anda.

Bundler saya sebagian besar merupakan pembungkus di sekitar compiler TypeScript. Penerapannya sebagian besar adalah ini, diadaptasi dari pengunjung react-refresh/babel .

Ini hanya hal sederhana yang berfungsi tetapi tidak selengkap pengunjung 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
}

Saya tidak yakin bagaimana menggunakan createSignatureFunctionForTransform pada awalnya tetapi itu hanya fungsi pabrik yang membuat mesin status kecil untuk setiap komponen. Jadi Anda memanggilnya sekali untuk setiap fungsi dengan tanda tangan kait statis (yang hanya berupa nilai buram, mirip dengan hash). Anda kemudian menyebutnya dari render untuk menyelesaikan pekerjaan penyiapannya.

Itu mengubah sesuatu seperti ini:

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

Menjadi ini:

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

Perhatikan pengunjung tidak lengkap. Ini hanya berhubungan dengan kasus penggunaan paling dasar.

Saya agak bingung dengan penggunaan window . Bagi saya tidak terlihat seolah-olah itu benar-benar perlu karena implementasinya ditukar? Apa gunanya itu?

@bayu_joo

Saya pikir implementasi di Metro tidak menggunakan window melainkan cakupan global .

Saya tidak tahu tentang alasan asli tentang implementasi ini, tetapi ini berguna dari pengalaman saya - ini memastikan logika yang dibutuhkan sebenarnya tidak tergantung pada logika penyegaran cepat (yang berarti react-refresh/babel transformasi dapat digunakan dengan hampir semua bundler). Seperti halnya swapping, ini juga bertindak sebagai penjaga untuk memastikan modul yang tidak seharusnya diproses oleh runtime tidak akan diproses:

Pertimbangkan kasus di mana @babel/runtime digunakan, yang akan memasukkan pembantu sebagai impor ke bundel dan Anda hanya ingin kode HMR non node_modules . Jika Anda tidak menginisialisasi helper kosong terlebih dahulu dan masih menugaskan pembantu ke ruang lingkup global, kasus langka mungkin terjadi di mana pembantu yang diinjeksi Babel akan memanggil cleanup sebelum modul lahan pengguna benar-benar menyelesaikan inisialisasi (karena mereka masih anak-anak) impor).

Apakah halaman ini membantu?
0 / 5 - 0 peringkat