React: Как мы должны настраивать приложения для HMR теперь, когда Fast Refresh заменяет react-hot-loader?

Созданный на 29 авг. 2019  ·  85Комментарии  ·  Источник: facebook/react

Дэн Абрамов упомянул, что Devtools v4 сделает react-hot-loader устаревшим: https://twitter.com/dan_abramov/status/1144715740983046144?s=20

Мне:
У меня есть такой крючок:
require("react-reconciler")(hostConfig).injectIntoDevTools(opts);
Но HMR всегда работал полностью без него. Это теперь новое требование?

Дэн:
Да, это то, что использует новый механизм. Новому механизму не нужен «response-hot-loader», поэтому к моменту обновления вы захотите удалить этот пакет. (Это довольно агрессивно)

Однако я не вижу упоминания о HMR в документации Devtools; Теперь, когда react-hot-loader устарело (а вместе с ним и метод require("react-hot-loader/root").hot ), как нам настроить приложения для HMR в:

  • Реагировать на приложения DOM
  • React Native приложения
  • Реагировать на пользовательские приложения отрисовки

Меня особенно заинтересует руководство по миграции специально для тех, кто уже настроил HMR с помощью react-hot-loader .

Кроме того, для HMR имеет ли значение, используем ли мы автономные инструменты разработки или расширение для браузера Devtools?

Question

Самый полезный комментарий

Хорошо, поехали.

Что такое быстрое обновление?

Это повторная реализация «горячей перезагрузки» при полной поддержке React. Первоначально он поставляется для React Native, но большая часть реализации не зависит от платформы. Планируется использовать его повсеместно - как замену чисто пользовательским решениям (например, react-hot-loader ).

Могу ли я использовать быстрое обновление в Интернете?

Теоретически да, это план. На практике кому-то нужно интегрировать его с распространенными в сети сборщиками (например, Webpack, Parcel). Я еще не успел это сделать. Может кто захочет забрать. Этот комментарий является приблизительным руководством к тому, как вы это сделаете.

Из чего он состоит?

Fast Refresh основан на совместной работе нескольких компонентов:

  • Механизм «горячей замены модуля» в модульной системе.

    • Обычно это также предоставляется сборщиком.

    • Например, в веб-пакете module.hot API позволяет это сделать.

  • React renderer 16.9.0+ (например, React DOM 16.9)

    • Или [email protected] или выше для пользовательских средств визуализации

  • react-refresh/runtime точка входа
  • Плагин react-refresh/babel Babel

Возможно, вы захотите поработать над частью интеграции. Т.е. интеграция react-refresh/runtime с механизмом "горячей замены модуля" Webpack.

На что похожа интеграция?

⚠️⚠️⚠️ Чтобы быть ясным, ЭТО РУКОВОДСТВО ДЛЯ ЛЮДЕЙ, КОТОРЫЕ ХОТЯТ САМОСТОЯТЕЛЬНО ВЫПОЛНИТЬ ИНТЕГРАЦИЮ. ДЕЙСТВУЙТЕ СВОЕ ВНИМАНИЕ!

Вот несколько вещей, которые вы хотите сделать минимально:

  • Включите HMR в вашем сборщике (например, webpack)
  • Убедитесь, что React 16.9.0+
  • Добавьте react-refresh/babel в свои плагины Babel

В этот момент ваше приложение должно аварийно завершить работу. Он должен содержать вызовы неопределенных функций $RefreshReg$ и $RefreshSig$ .

Затем вам нужно создать новую точку входа JS, которая должна запускаться перед любым кодом в вашем приложении , включая react-dom (!) Это важно; если он запускается после 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 посылает вызовы этим функциям, а затем наша интеграция, описанная выше, связывает эти вызовы с идентификатором модуля. Таким образом, среда выполнения получает строки типа "path/to/Button.js Button" при регистрации компонента. (Или, в случае с webpack, идентификаторы будут числами.) Не забывайте, что и преобразование Babel, и эта упаковка должны выполняться только в режиме разработки.

В качестве альтернативы обертыванию кода модуля, возможно, есть способ добавить такую ​​команду try / finally в том месте, где сборщик фактически инициализирует фабрику модулей. Как мы делаем здесь, в Метро (сборщик RN). Это, вероятно, было бы лучше, потому что нам не нужно было бы раздувать каждый модуль или беспокоиться о введении недопустимого синтаксиса, например, при переносе import в try / finally .

Как только вы это подключите, у вас возникнет последняя проблема. Ваш сборщик не знает, что вы обрабатываете обновления, поэтому он, вероятно, все равно перезагружает страницу. Вы должны сказать ему, чтобы не делать этого. Это снова зависит от сборщика, но я предлагаю подход, чтобы проверить, все ли


// ...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. Вот как вы решаете, принимать обновление или нет. Я не копировал и вставлял его сюда, но эта реализация могла бы стать хорошим началом. (В этом коде Refresh означает экспорт react-refresh/runtime ).

Вы также захотите вручную зарегистрировать весь экспорт, потому что преобразование Бабеля будет вызывать только $RefreshReg$ для функций. Если вы этого не сделаете, обновления классов не будут обнаружены.

Наконец, функция enqueueUpdate() будет чем-то совместно используемым между модулями, которые устраняют проблемы и выполняют фактическое обновление React.

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

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

К этому моменту у вас уже должно что-то работать.

Нюансы

Есть некоторые базовые ожидания опыта, которые меня волнуют, которые входят в брендинг «Быстрое обновление». Он должен уметь корректно восстанавливаться после синтаксической ошибки, ошибки инициализации модуля или ошибки рендеринга. Я не буду вдаваться в подробности этих механизмов, но я твердо убежден, что вы не должны называть свой эксперимент «Быстрое обновление», пока он не справится с этими случаями хорошо.

К сожалению, я не знаю, может ли webpack поддерживать все это, но мы можем попросить о помощи, если вы дойдете до некоторого рабочего состояния, но затем застрянете. Например, я заметил, что API accept() webpack затрудняет восстановление после ошибок (вам нужно принять _предыдущую_ версию модуля после ошибки), но есть способ обойти это. Еще одна вещь, к которой нам нужно вернуться, - это автоматически «регистрировать» все экспорты, а не только те, которые обнаруживаются плагином Babel. На данный момент давайте проигнорируем это, но если у вас есть что-то, что работает, например, для webpack, я могу посмотреть, как это отполировать.

Точно так же нам нужно будет интегрировать его с «окном ошибки», похожим на react-error-overlay в приложении Create React. В этом есть нюанс, например, когда вы исправляете ошибку, сообщение об ошибке должно исчезать. Это также требует некоторой дополнительной работы, которую мы можем сделать после того, как фундамент будет на месте.

Дайте знать, если у вас появятся вопросы!

Все 85 Комментарий

Есть некоторая путаница. Новый DevTools не поддерживает горячую перезагрузку (и не имеет ничего общего с перезагрузкой). Скорее, изменения в горячей перезагрузке, над которыми работал Дэн, используют «крючок», который DevTools и React используют для общения. Он добавляется в середину, чтобы выполнить перезагрузку.

Я отредактировал заголовок, чтобы убрать упоминание о DevTools (так как это может вызвать путаницу).

Что касается вопроса о том, как следует использовать новый HMR, я не думаю, что знаю последние мысли там. Я вижу, что у @gaearon есть пиар в
https://github.com/facebook/create-react-app/pull/5958

Что касается вопроса о том, как следует использовать новый HMR, я не думаю, что знаю последние мысли там. Я вижу, что у @gaearon есть пиар в

Уточняю читателям, что PR очень устарел и уже не актуален.


Мне нужно написать кое-что о том, как работает Fast Refresh и как его интегрировать. Еще не успел.

Хорошо, поехали.

Что такое быстрое обновление?

Это повторная реализация «горячей перезагрузки» при полной поддержке React. Первоначально он поставляется для React Native, но большая часть реализации не зависит от платформы. Планируется использовать его повсеместно - как замену чисто пользовательским решениям (например, react-hot-loader ).

Могу ли я использовать быстрое обновление в Интернете?

Теоретически да, это план. На практике кому-то нужно интегрировать его с распространенными в сети сборщиками (например, Webpack, Parcel). Я еще не успел это сделать. Может кто захочет забрать. Этот комментарий является приблизительным руководством к тому, как вы это сделаете.

Из чего он состоит?

Fast Refresh основан на совместной работе нескольких компонентов:

  • Механизм «горячей замены модуля» в модульной системе.

    • Обычно это также предоставляется сборщиком.

    • Например, в веб-пакете module.hot API позволяет это сделать.

  • React renderer 16.9.0+ (например, React DOM 16.9)

    • Или [email protected] или выше для пользовательских средств визуализации

  • react-refresh/runtime точка входа
  • Плагин react-refresh/babel Babel

Возможно, вы захотите поработать над частью интеграции. Т.е. интеграция react-refresh/runtime с механизмом "горячей замены модуля" Webpack.

На что похожа интеграция?

⚠️⚠️⚠️ Чтобы быть ясным, ЭТО РУКОВОДСТВО ДЛЯ ЛЮДЕЙ, КОТОРЫЕ ХОТЯТ САМОСТОЯТЕЛЬНО ВЫПОЛНИТЬ ИНТЕГРАЦИЮ. ДЕЙСТВУЙТЕ СВОЕ ВНИМАНИЕ!

Вот несколько вещей, которые вы хотите сделать минимально:

  • Включите HMR в вашем сборщике (например, webpack)
  • Убедитесь, что React 16.9.0+
  • Добавьте react-refresh/babel в свои плагины Babel

В этот момент ваше приложение должно аварийно завершить работу. Он должен содержать вызовы неопределенных функций $RefreshReg$ и $RefreshSig$ .

Затем вам нужно создать новую точку входа JS, которая должна запускаться перед любым кодом в вашем приложении , включая react-dom (!) Это важно; если он запускается после 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 посылает вызовы этим функциям, а затем наша интеграция, описанная выше, связывает эти вызовы с идентификатором модуля. Таким образом, среда выполнения получает строки типа "path/to/Button.js Button" при регистрации компонента. (Или, в случае с webpack, идентификаторы будут числами.) Не забывайте, что и преобразование Babel, и эта упаковка должны выполняться только в режиме разработки.

В качестве альтернативы обертыванию кода модуля, возможно, есть способ добавить такую ​​команду try / finally в том месте, где сборщик фактически инициализирует фабрику модулей. Как мы делаем здесь, в Метро (сборщик RN). Это, вероятно, было бы лучше, потому что нам не нужно было бы раздувать каждый модуль или беспокоиться о введении недопустимого синтаксиса, например, при переносе import в try / finally .

Как только вы это подключите, у вас возникнет последняя проблема. Ваш сборщик не знает, что вы обрабатываете обновления, поэтому он, вероятно, все равно перезагружает страницу. Вы должны сказать ему, чтобы не делать этого. Это снова зависит от сборщика, но я предлагаю подход, чтобы проверить, все ли


// ...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. Вот как вы решаете, принимать обновление или нет. Я не копировал и вставлял его сюда, но эта реализация могла бы стать хорошим началом. (В этом коде Refresh означает экспорт react-refresh/runtime ).

Вы также захотите вручную зарегистрировать весь экспорт, потому что преобразование Бабеля будет вызывать только $RefreshReg$ для функций. Если вы этого не сделаете, обновления классов не будут обнаружены.

Наконец, функция enqueueUpdate() будет чем-то совместно используемым между модулями, которые устраняют проблемы и выполняют фактическое обновление React.

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

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

К этому моменту у вас уже должно что-то работать.

Нюансы

Есть некоторые базовые ожидания опыта, которые меня волнуют, которые входят в брендинг «Быстрое обновление». Он должен уметь корректно восстанавливаться после синтаксической ошибки, ошибки инициализации модуля или ошибки рендеринга. Я не буду вдаваться в подробности этих механизмов, но я твердо убежден, что вы не должны называть свой эксперимент «Быстрое обновление», пока он не справится с этими случаями хорошо.

К сожалению, я не знаю, может ли webpack поддерживать все это, но мы можем попросить о помощи, если вы дойдете до некоторого рабочего состояния, но затем застрянете. Например, я заметил, что API accept() webpack затрудняет восстановление после ошибок (вам нужно принять _предыдущую_ версию модуля после ошибки), но есть способ обойти это. Еще одна вещь, к которой нам нужно вернуться, - это автоматически «регистрировать» все экспорты, а не только те, которые обнаруживаются плагином Babel. На данный момент давайте проигнорируем это, но если у вас есть что-то, что работает, например, для webpack, я могу посмотреть, как это отполировать.

Точно так же нам нужно будет интегрировать его с «окном ошибки», похожим на react-error-overlay в приложении Create React. В этом есть нюанс, например, когда вы исправляете ошибку, сообщение об ошибке должно исчезать. Это также требует некоторой дополнительной работы, которую мы можем сделать после того, как фундамент будет на месте.

Дайте знать, если у вас появятся вопросы!

Синтаксические ошибки / ошибки инициализации должны быть «достаточно простыми», чтобы их можно было каким-либо образом обработать, прежде чем сообщать React о начале рендеринга, но как ошибки рендеринга будут взаимодействовать с границами ошибок?

Если произойдет ошибка рендеринга, она вызовет ближайшую границу ошибки, которая будет снимать себя в состояние ошибки, и нет общего способа сообщить границам ошибки, что их дочерние элементы волшебным образом _возможно_ исправлены после обновления в реальном времени. Должен ли каждый обновляемый компонент бесплатно получать собственную границу ошибок, или обработка ошибок работает по-другому в согласователе, когда при инициализации обнаруживается поддержка времени выполнения?

все экспортируемые компоненты являются компонентами React, и в этом случае "принять" обновление

Есть ли способ обнаружить такие компоненты? Насколько я понимаю - нет. За исключением export.toString().indexOf('React')>0 , но он перестанет работать с любым примененным HOC.
Кроме того, _self принятие_ в конце файла не подвержено ошибкам - новый дескриптор принятия не будет установлен, и следующее обновление будет пузыриться до более высокой границы, поэтому был создан require("react-hot-loader/root").hot .

В любом случае - похоже, что если бы кто-то выбросил весь код, специфичный для реакции, из react-hot-loader , не трогая внешний API - этого было бы достаточно, и это применимо ко всем существующим установкам.

Использование 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, оно всегда подключает их заново.

Если границ нет, но корень не удалось обновить, Fast Refresh повторит рендеринг этого корня с его последним элементом.

Если при монтировании рут не удалось, runtime.hasUnrecoverableErrors() сообщит вам об этом. Затем вам нужно принудительно перезагрузить. Мы разберемся с этим делом позже, у меня еще не было времени, чтобы это исправить.

Использование response-refresh / babel 0.4.0 дает мне эту ошибку для большого количества файлов:

Подать новую проблему, пожалуйста?

Есть ли способ обнаружить такие компоненты?

Я связался со своей реализацией, которая сама использует Runtime.isLikelyAReactComponent() . Это не идеально, но достаточно хорошо.

новый дескриптор принятия не будет установлен, и следующее обновление будет пузыриться до более высокой границы

Вы можете привести пример? Я не слежу. В любом случае, это что-то особенное для сборщика. Я заставил Metro делать то, что я хотел. Мы можем попросить webpack добавить что-нибудь, если нам не хватает API.

Здесь цель состоит в том, чтобы повторно выполнить как можно меньше модулей, обеспечивая при этом согласованность. Мы не хотим, чтобы при большинстве изменений обновлялись пузыри в корневом каталоге.

похоже, что если бы вы выбросили весь код, специфичный для реакции, из response-hot-loader, сохраняя внешний API нетронутым

Может быть, хотя я бы тоже хотел убрать контейнер верхнего уровня. Я также хочу более тесную интеграцию с окном ошибок. Может быть, это еще можно назвать react-hot-loader .

Кстати, я отредактировал свое руководство, включив в него недостающий элемент, который я забыл - звонок performReactRefresh . Это то, что на самом деле планирует обновления.

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

Я бы не чувствовал себя в безопасности с такой логикой. Даже если все CapitalizedFunctions почти всегда являются компонентами React, многие модули (мои) также имеют другие возможности экспорта. Например, _exports-for-tests_. Это не проблема, но создает некоторую _предсказуемость_ - горячая граница может быть создана в любой момент ... или не создана после одного изменения строки.
Что могло сломать тест isLikelyComponentType :

  • экспортировано mapStateToProps (для тестов, не используется в производственном коде)
  • экспортировал hook (и это нормально)
  • экспортировал Class который может не быть классом реакции (не будет, но должен)

Итак - будут случаи, когда горячая граница должна быть установлена, но не будет, и будут случаи, когда горячая граница будет установлена, но не будет. Звучит как старая добрая нестабильная горячая перезагрузка, которая нам обоим не нравится :)

Есть одно место, где применение горячей границы не будет таким непредсказуемым и вполне ожидаемым - это граница thing или domain , или индекс каталога, то есть реэкспорт index.js «общедоступный API» из Component.js в том же каталоге (не афайк в стиле Facebook).

Другими словами - все, как в Metro, но с большим количеством ограничений. Все остальное, например правило линтинга, устанавливающее такую ​​границу для любого компонента с отложенной загрузкой, можно использовать для обеспечения правильного поведения.

Кстати говоря, горячее быстрое обновление справится с Lazy? Ожидается ли, что граница будет с другой стороны import ?

Попробовал быстро увидеть волшебство в браузере, и это так здорово :) Я сделал простейшую вещь, то есть жестко закодировал весь код инструментария, так что плагинов webpack пока нет

Kapture 2019-09-07 at 23 09 04

Репо здесь: https://github.com/pekala/react-refresh-test

Просто любопытно, но для webpack, не могли бы вы просто добавить плагин babel, чтобы обернуть try / finally? Просто хочу убедиться, что я что-то не упускаю, прежде чем попробовать.

Плагин Babel не зависит от среды. Я бы хотел, чтобы это было так. Он ничего не знает о модулях или механизме распространения обновлений. Они различаются в зависимости от комплекта поставки.

Например, в Metro вообще нет преобразования "попытка / окончание". Вместо этого я помещаю try / finally в среду выполнения сборщика , где она вызывает фабрику модулей. Это было бы идеально и с веб-пакетом, но я не знаю, позволяет ли он вам подключиться к среде выполнения таким образом.

Вы, конечно, можете создать еще один плагин Babel для упаковки. Но это ничего не дает вам, если вы делаете это через webpack. Так как это в любом случае зависит от веб-пакета. И может сбивать с толку то, что вы можете случайно запустить этот плагин Babel в другой среде (не в веб-пакете), где это не имеет смысла.

Вы можете, подключившись к крючку водопада compilation.mainTemplate.hooks.require . Предыдущий вызов его является телом по умолчанию для функции __webpack_require__ , поэтому вы можете нажать на ловушку, чтобы обернуть содержимое в блок try/finally .

Проблема заключается в получении ссылки на React внутри __webpack_require__ . Это возможно, но может потребовать некоторой степени повторного входа и защиты рекурсии.

Для получения дополнительной информации проверьте MainTemplate.js и web/JsonpMainTemplatePlugin.js в исходном коде веб-пакета. JsonpMainTemplatePlugin само по себе просто попадает в кучу крючков из MainTemplate.js так что это, вероятно, "мясо", которое вам нужно.

Вот изобретательный прототип, который я собрал вместе, он эффективно выполняет то, что Дэн описал выше. Это ужасно неполно, но доказывает, что реализация lo-fi в веб-пакете: https://gist.github.com/maisano/441a4bc6b2954205803d68deac04a716

Некоторые примечания:

  • react-dom здесь жестко запрограммирован, поэтому это не будет работать с пользовательскими модулями визуализации или подпакетами (например, react-dom/profiling ).
  • Я не слишком глубоко изучал, как работают все варианты шаблонов webpack, но способ, которым я обернул выполнение модуля, довольно хакерский. Я не уверен, что этот пример будет работать, если, скажем, использовать цель библиотеки umd .

Проблема заключается в получении ссылки на React внутри __webpack_require__. Это возможно, но может потребовать некоторой степени повторного входа и защиты рекурсии.

Я предполагаю, что вы имеете в виду ссылку на Refresh Runtime.

В Metro я решил эту проблему, сделав require.Refresh = RefreshRuntime как можно раньше. Затем внутри реализации require я могу прочитать свойство самой функции require . Он не будет доступен сразу, но не имеет значения, если мы установим его достаточно рано.

@maisano Мне пришлось изменить ряд вещей, и в конечном итоге я не вижу функцию .accept, вызываемую webpack. Я пробовал как .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",

Я знаю, что это идет вразрез с их API, который хочет, чтобы это было "errorCallback". Я помню, как столкнулся с этим, особенно много лет назад, работая над нашим внутренним HMR, и в конечном итоге мы написали наш собственный сборщик пакетов. Я считаю, что parcel поддерживает API обратного вызова "самопринятие". Может быть, стоит открыть вопрос о webpack и посмотреть, сможем ли мы его объединить? @sokra

Итак ... Я доработал плагин на основе работы @maisano :
https://github.com/pmmmwh/react-refresh-webpack-plugin
(Я написал это на TypeScript, потому что я не доверяю себе возиться с внутренностями webpack, когда начинал, я могу преобразовать это в простой JS / Flow)

Я попытался устранить необходимость в загрузчике для внедрения кода горячего модуля с классами webpack Dependency , но, похоже, это потребует повторного анализа всех модулей (потому что даже со всеми встроенными функциями нам все еще нужен ссылка на react-refresh/runtime где-то).

Другая проблема заключается в том, что нет простых способов (afaik) для обнаружения файлов, подобных JavaScript, в webpack - например, html-webpack-plugin использует тип javascript/auto , поэтому я жестко запрограммировал то, что кажется приемлемая маска файла (JS / TS / Flow) для инъекции загрузчика.

Я также добавил исправление ошибок (по крайней мере, синтаксическую ошибку) на основе комментария @gaearon в этой 5-летней давности . Далее идет восстановление после ошибок реакции - я подозреваю, что это можно сделать, введя глобальную границу ошибки (вроде как AppWrapper из react-hot-loader ), которая также устранит интерфейс окна ошибок, но не у меня еще есть время, чтобы добраться до этого.

Также можно избежать проблемы, вызванной @natew , - это достигается разделением вызова enqueueUpdate вызова hot.accpet(errorHandler) .

@pmmmwh Какое время! Я только что создал репо, которое построено на / немного изменило работу, которой я поделился в сущности.

В любом случае я не дошел до обработки ошибок, хотя плагин здесь немного надежнее, чем исходный подход, который я использовал.

Далее идет восстановление после ошибок реакции - я подозреваю, что это можно сделать, введя глобальную границу ошибки (вроде как AppWrapper в react-hot-loader), которая также будет заниматься интерфейсом окна ошибок, но у него не было времени, чтобы добраться до это пока еще.

Это уже должно работать из коробки. Здесь нет необходимости в настраиваемой границе ошибки или переносе.

Далее идет восстановление после ошибок реакции - я подозреваю, что это можно сделать, введя глобальную границу ошибки (вроде как AppWrapper в react-hot-loader), которая также будет заниматься интерфейсом окна ошибок, но у него не было времени, чтобы добраться до это пока еще.

Это уже должно работать из коробки. Здесь нет необходимости в настраиваемой границе ошибки или переносе.

@gaearon Странно. Я пробовал выдавать ошибки в компонентах функции рендеринга - если ошибка возникает в return , HMR работает, но если она возникает где-то еще, иногда она не работает.

@pmmmwh Какое время! Я только что создал репо, которое построено на / немного изменило работу, которой я поделился в сущности.

В любом случае я не дошел до обработки ошибок, хотя плагин здесь немного надежнее, чем исходный подход, который я использовал.

@maisano Что мне сказать? Я действительно начал работать над этим и застрял с проблемой внедрения зависимостей на прошлых выходных ... Ваша суть предоставила мне выход: tada:

если ошибка возникает в ответ, HMR работает, но если она возникает где-то еще, иногда не работает.

Мне нужно больше подробностей о том, что именно вы пробовали и что вы имеете в виду под «работает» и «не работает».

Есть много вещей, которые могут пойти не так, если интеграция сборщика модулей не будет реализована правильно (это тема или эта тема). Я ожидал, что в самом React нет ничего, что препятствовало бы восстановлению после ошибок, возникших во время редактирования. Вы можете убедиться, что он работает в React Native 0.61 RC3.

@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 , и это, вероятно, уничтожит все вложенное дерево.

Я ожидал ленивых работать. Может что-то сломалось. Мне нужно посмотреть репродуктивный случай.

Поскольку существует несколько прототипов, следует ли выбрать один и затем перейти к обсуждению его проблем? И повторить там.

Есть:
https://github.com/maisano/react-refresh-plugin
и:
https://github.com/pmmmwh/react-refresh-webpack-plugin
Я создал форк плагина pmmmwh, который работает с [email protected] (также исправляет именованный экспорт):
https://github.com/WebHotelier/webpack-fast-refresh

А как насчет react-hot-loader ?

react-hot-loader перенес почти все функции из fast refresh , но есть несколько исторических и интеграционных моментов, которые не позволяют выполнить обратный перенос всего, и, честно говоря, нет смысла повторно реализовывать их в терминах "rhl" . Так что - пусть на пенсию.

Мне нужно больше подробностей о том, что именно вы пробовали и что вы имеете в виду под «работает» и «не работает».

Есть много вещей, которые могут пойти не так, если интеграция сборщика модулей не будет реализована правильно (это тема или эта тема). Я ожидал, что в самом React нет ничего, что препятствовало бы восстановлению после ошибок, возникших во время редактирования. Вы можете убедиться, что он работает в React Native 0.61 RC3.

После нескольких настроек я могу убедиться, что он работает.

Однако - похоже, что плагин babel не работал для классов. Я проверил, и, похоже, это происходит независимо от реализации, поскольку весь введенный код и react-refresh/runtime работают правильно. Я не уверен, предназначено ли это или это специфично для веб-пакета, если это последнее, я могу попробовать исправить это завтра. (Я также тестировал это только с предустановкой метро, ​​воспроизведите суть здесь )

Я уверен, что это работает для RN, но на моем текущем компьютере у меня нет среды, удобной для тестирования на RN, поэтому, если вы можете указать мне на реализацию плагина babel в метро или на преобразования, которые были бы действительно полезны.

Поскольку существует несколько прототипов, следует ли выбрать один и затем перейти к обсуждению его проблем? И повторить там.

Может, пойдем сюда ? С момента моего последнего комментария я перенес весь проект в простой JS и добавил некоторые исправления в очереди обновлений. Мне не нужно переносить плагин для webpack @ 5 , но после прочтения вилки @apostolos и новой логики HMR в webpack @ next исправления должны быть простыми.

Да, плагин Babel не регистрирует классы. Предполагается, что это произойдет на уровне модульной системы. Каждый экспорт следует проверять на предмет «вероятности» использования компонента React. (Функция проверки предоставляется средой выполнения.) Если это правда, зарегистрируйте экспорт, как это сделал бы плагин Babel. Присвойте ему идентификатор вида filename exports%export_name . Это то, что заставляет классы работать в Metro, поскольку плагин Babel их не найдет.

Другими словами, поскольку мы в любом случае не можем сохранить состояние класса, мы могли бы с таким же успехом доверить «локализацию» их границам экспорта модуля вместо того, чтобы пытаться найти их встроенными в исходный код с помощью преобразования. Экспорт должен действовать как «уловка всего» для компонентов, которых мы не нашли с помощью плагина.

Mailchimp начал использовать форк плагина, которым я поделился последним. Он был немного переработан, и люди, которые решили его использовать, выглядят вполне счастливыми. Мы продолжим повторять его локально. Я планирую удалить вилку и публиковать обновления в апстриме, когда это будет немного дальше.

@gaearon Мысли о добавлении символа, который мы можем прикрепить к вещам, которые, как мы знаем, безопасны для обновления, но не к компонентам? Например, у нас есть такой шаблон:

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

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

Где create просто возвращает объект. На данный момент я исправил его со своей стороны, но мы могли бы легко добавить туда символ к объекту экспорта по умолчанию, который указывает, что это безопасный файл. Не уверен, что это лучший образец.

Изменить: я понял, что это может войти в реализацию обновления! Я думал, что может быть лучше во время выполнения, но, возможно, нет. При таком большом количестве различных имплементаций загрузчика может быть лучше иметь стандартный способ.

Давайте перейдем на 10 лет вперед. Как выглядит ваша кодовая база? Разрешить здесь, запретить там? Как поддерживать эти флаги в актуальном состоянии? Как рассуждать? Например, есть _ безопасные для обновления_ местоположения и _ небезопасные_, вы должны сохранить или не можете правильно согласовать их по какой-то причине. Какие причины в каждом случае являются уважительными?

  • какие symbols вас будет больше - примерно force allow reload или force disallow reload
  • почему вы можете захотеть снизить границу распространения обновления (т.е. принять обновление на «этой» границе модуля) или поднять ее (т.е. принять обновление на «этой» границе модуля)
  • что произойдет, если не будут установлены границы? Это только проблема производительности или может случиться что-то более серьезное?

Привет, ребята 👋 Я хочу помочь здесь. Договорились ли мы об одном репо / усилии?

Это репо разделяет @pmmmwh?
https://github.com/pmmmwh/react-refresh-webpack-plugin

Или это репо принадлежит @maisano?
https://github.com/maisano/react-refresh-plugin

Похоже, что тот, что написал @pmmmwh совсем недавно. Если я не услышу иное, я предполагаю, что это тот, на котором нужно сосредоточиться.

Реализация в Parcel 2 началась здесь: https://github.com/parcel-bundler/parcel/pull/3654

Летом!

Для всех, кто ищет это, реализация проектов React Refresh для Rollup с использованием Nollup для разработки: https://github.com/PepsRyuu/rollup-plugin-react-refresh

Наверное, не самая чистая реализация, но работает.

Для решений webpack, похоже, не было официального выпуска вышеуказанных плагинов, поэтому кажется, что лучшим решением HMR для реакции является библиотека Дэна: https://github.com/gaearon/react-hot-loader

Мы только что отправили Parcel 2 alpha 3 с поддержкой Fast Refresh прямо из коробки! Не стесняйтесь попробовать. 😍 https://twitter.com/devongovett/status/1197187388985860096?s=20

🥳 добавлено примечание об устаревании в RHL 🥳

Рецепт, который я использовал, чтобы опробовать это в приложениях CRA, используя текущую работу @pmmmwh , react-app-rewired и 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

Изменить ./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

И ... v0.1.0 для Webpack только что отправлено 🎉

@ drather19 @jihchi
Вы, ребята, возможно, захотите перейти на эту версию - она ​​включает в себя более унифицированный интерфейс, а также множество исправлений ошибок в первоначальной реализации.

@pmmmwh поддерживает ts-loader + babel-loader ?

@pmmmwh поддерживает ts-loader + babel-loader ?

Я тестировал TS только с помощью Babel, и он работает, поэтому, если он не работает, когда вы используете загрузчики ts + babel, не стесняйтесь сообщать о проблеме :)

@ drather19 Я пробовал клонировать и запустить ваше репо, но сервер разработки не запускается.

Окружающая обстановка,
ОС - 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 Я пробовал клонировать и запустить ваше репо, но сервер разработки не запускается.

Окружающая обстановка,
ОС - 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 плагина и будет выпущено завтра.

Мне удалось заставить плагин webpack @pmmmwh работать с приложением TypeScript React с помощью babel. Однако инкрементальные сборки занимают около 12 секунд вместо ~ 2 секунд с использованием только ts-loader. Я собираюсь продолжить играть с этим, чтобы увидеть, не упустил ли я что-то на стороне конфигурации babel, что приближает производительность, но пока что это отмывка по сравнению с ts-loader и полными обновлениями.

@IronSean Пожалуйста, сообщите об этом в

Я собираюсь продолжить играть с этим, чтобы увидеть, не упустил ли я что-то на стороне конфигурации babel, что приближает производительность, но пока что это отмывка по сравнению с ts-loader и полными обновлениями.

Не возражаете разместить там свою конфигурацию / настройку? Я не смогу разобраться в проблемах без дополнительного контекста.

@pmmmwh Я открыл эту проблему, чтобы переместить обсуждение в ваше репо, как только подтвердил, что действительно ваш плагин имеет значение:
https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/20

Будет ли react-refresh ( React Fast Refresh ?) Работать с Preact, или react-hot-loader - это долгосрочное решение для Preact?

@Jumblemuddle, который зависит от Preact, но они должны иметь возможность интегрироваться с Fast Refresh, если захотят.

Для CRA людей, желающих работать с Fast Refresh, мне повезло больше с craco (по сравнению с react-app-rewired + customize-cra) теперь через следующие 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 })],
        [],
      ),
    ],
  },
};

В частности, добавление webpackConfig.optimization.runtimeChunk = false; позволит вам добавлять / удалять хуки и по-прежнему изящно быстро обновлять.

Теперь еще больше наслаждайтесь улучшенными возможностями. Спасибо @ mmhand123 за подсказку через https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/25! (<- решено!)

Основываясь на предложении @ drather19, я опубликовал плагин customize-cra, чтобы упростить задачу. См. Esetnik / customize-cra-react-refresh .

Благодаря @drather19 я немного

Сначала установите следующие пакеты в подпакеты, для которых вы хотите включить быстрое обновление:

"@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 Ожидаем ли мы, что Fast Refresh станет доступным в CRA по умолчанию в какой-то момент времени?
если да, то что для этого требуется?

Для этого требуется определенная работа :-), которая в настоящее время выполняется.

если использовать HMR будут вызываться функции? например componentDidMount.
Я использую response-proxy, и будет вызываться componentDidMount.
А React 15.X может использовать Fast Refresh?

  • componentDidMount будет вызван. Как и unmount - классы будут перезагружены полностью.
  • и сейчас хорошее время, чтобы перестать использовать react-proxy . Что ж, тебе, наверное, стоило остановиться несколько лет назад.
  • 15.X ? - точно нет. Быстрое обновление __ является частью__ реакции и поэтому существует только в современных версиях.

так что мы должны использовать Fast Refresh или react-hot-loader для замены react-proxy?
Есть ли способ предотвратить выполнение функций (componentDidMount) для HMR? - он вызовет метод для получения новых данных.

Как мне использовать react-hot-loader в JIT? - Компиляция в браузере в реальном времени

  • так что мы должны использовать Fast Refresh или react-hot-loader для замены react-proxy?

    Сначала попробуйте fast refresh , затем RHL

  • Есть ли способ предотвратить выполнение функций (componentDidMount) для HMR? - он вызовет метод для получения новых данных.

    (используйте хуки ...), не полагайтесь на жизненный цикл компонента, извлекайте данные при необходимости. Попробуйте react-query , swr или другие решения.

Что касается вопроса о том, как следует использовать новый HMR, я не думаю, что знаю последние мысли там. Я вижу, что у @gaearon есть пиар в

Уточняю читателям, что PR очень устарел и уже не актуален.

Мне нужно написать кое-что о том, как работает Fast Refresh и как его интегрировать. Еще не успел.

На сегодняшний день этот PR все еще открыт. Было бы неплохо, если бы открытыми оставались только соответствующие ОР, у которых еще есть шанс объединиться, чтобы иметь лучший обзор. Если вы просто храните их в качестве справки, я бы рекомендовал переместить материал в ветку, тег или другой репозиторий.

Я все время получаю Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly. Я следил за документацией, но кажется, что я что-то пропустил?

Я все время получаю сообщение об ошибке: [React Refresh] Горячая замена модуля (HMR) не включена! React Refresh требует правильной работы HMR. Я следил за документацией, но кажется, что я что-то пропустил?

@silkfire Я предполагаю, что вы используете плагин webpack. Если да, задайте свой вопрос в репозитории плагинов webpack: https://github.com/pmmmwh/react-refresh-webpack-plugin/.

На сегодняшний день этот PR все еще открыт. Было бы неплохо, если бы открытыми оставались только соответствующие ОР, у которых еще есть шанс объединиться, чтобы иметь лучший обзор. Если вы просто храните их в качестве справки, я бы рекомендовал переместить материал в ветку, тег или другой репозиторий.

Я ценю ваше предложение, но из-за тысяч непрочитанных уведомлений мне иногда бывает трудно не забыть вернуться к старым PR. Я верю, что сопровождающие репозитория Create React App сделают правильный выбор и закроют его, если сочтут это бесполезным.

Я закрою это.

У нас есть 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. Я следил за документацией, но кажется, что я что-то пропустил?

Похоже, вы не включили Webpack HMR. Для получения дополнительной помощи, пожалуйста, сообщите о проблеме в репозиторий плагина.

Поскольку горячая замена теперь является частью React - если у нее есть отдельное место в документации React, указывающее на дополнительные библиотеки, которые будут использоваться с конкретными сборщиками и платформами, а также с объяснением некоторых все еще существующих ошибок, например, с самообновляющимся css модули.

Подобную информацию не следует хоронить в выпусках github и сообщениях в блогах.

@theKashey это в React, но реализация
Кроме того, есть реализация быстрого обновления, которая будет связана с приложением create-react-app, но она еще не выпущена: pmmmwh / response-refresh-webpack-plugin # 7. Возможно, это будет в следующей версии react-scripts.

Так что, вероятно, команда React в настоящее время считает неправильным говорить о Fast Refresh для реакции на эту экспериментальную фазу.

это в React, но реализация react-dom, например, только экспериментальная.

Для ясности, реализация в react-dom сама по себе стабильна, как и в React Native. Просто не все интеграции стабильны.

должен ли он иметь отдельное место в документации React, указывая на дополнительные библиотеки, которые будут использоваться с конкретными сборщиками и платформами, а также объясняет некоторые все еще существующие ошибки, например, с самообновляющимися модулями css.

Звучит разумно. Я был бы счастлив получить PR, добавив его в раздел Advanced Guides, возможно, на основе аналогичной страницы RN .

@gaearon
Мое приложение для реагирования в порядке с некоторыми стилизованными изменениями компонентов и правильно применяет эти изменения без каких-либо проблем.
Однако, когда я меняю какой-то код в редукторе Redux, все приложение жестко обновляется и теряет все состояния редукции.
Нужно ли мне использовать другие библиотеки, например redux-persist чтобы сохранить текущее состояние вместе с react-fast-refresh ?

Мы прошли полный круг и снова начнем

Вот как работает HMR низкого уровня, и это выходит за рамки ответственности за быстрое обновление. Пожалуйста, обратитесь к документации redux или webpack

Мы прошли полный круг и снова начнем

Вот как работает HMR низкого уровня, и это выходит за рамки ответственности за быстрое обновление. Пожалуйста, обратитесь к документации redux или webpack

Не могли бы вы поставить ссылку на полный круг?

@ jwchang0206 Убедитесь, что код , как это в вашем магазине.

Не могли бы вы поставить ссылку на полный круг?

Те же вопросы были заданы для React Hot Loader. Были даны те же ответы. Мы находимся в начале нового цикла.

@ jwchang0206 Посмотрите на Redux-переходников инжектор , небольшая библиотека , которую я написал для решения этого вопроса.
Это позволит вам поддерживать перезагрузку редукторов с помощью горячей перезагрузки.
Убедитесь, что вы следуете принципам неизменности редукторов в редукторах, и все будет работать гладко 💯
А если вы используете саги, вы можете использовать redux-sagas-injector .

@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 $ для объекта окна, но привязка к среде браузера дала мне проблемы при использовании Fast Refresh в React NativeScript. @pmmmwh пришел на помощь, адаптировав свой плагин Fast Refresh Webpack для преодоления связи Fast Refresh с браузером (возникшие и преодоленные проблемы обсуждались в этой теме: https://github.com/pmmmwh/react-refresh-webpack-plugin/ выпусков / 79). Интересно, будет ли вам полезен используемый подход при интеграции Fast Refresh в ваш собственный сборщик.

Мой сборщик в основном представляет собой оболочку компилятора 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 но это всего лишь фабричная функция, которая создает небольшой конечный автомат для каждого компонента. Таким образом, вы вызываете его один раз для каждой функции со статической сигнатурой хука (которая является просто непрозрачным значением, похожим на хеш). Затем вы вызываете его из рендера, чтобы он завершил настройку.

Меняется примерно так:

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 .

Я не знаю первоначального обоснования этой реализации, но по моему опыту он оказался полезным - он гарантирует, что фактическая логика требований не зависит от логики быстрого обновления (что означает, что преобразования react-refresh/babel могут использоваться практически с любыми сборщик). Как и свопинг, он также действует как защита, гарантирующая, что модули, которые не должны обрабатываться средой выполнения, не будут обработаны:

Рассмотрим случай, когда используется @babel/runtime , который будет внедрять помощники в качестве импорта в пакет, и вам нужен только HMR, отличный node_modules кода cleanup до того, как модуль пользовательского уровня фактически завершит инициализацию (потому что они являются дочерними импорт).

Была ли эта страница полезной?
0 / 5 - 0 рейтинги