react-refresh-webpack-plugin icon indicating copy to clipboard operation
react-refresh-webpack-plugin copied to clipboard

feat: acceptSelf

Open cha0s opened this issue 1 year ago • 2 comments

Problem

The runtime hot.accepts a refreshed module unconditionally, however in advanced circumstances it might be desired for the HMR invalidation to propagate up to a parent watching for changes. Currently, attempts to accept from any parent fail.

In order to allow parents to monitor the context for changes, this PR adds a new option to the plugin: acceptSelf.

Example use-case

Watching a require.context ID for changes:

const context = require.context('./my-components');
module.hot.accept(context.id, () => {
  // Unreachable
});
// Even e.g. this will never work:
require.cache[context.id].hot.accept(context.resolve(context.keys()[0]), () => {
  // Still unreachable
});

New documentation

acceptSelf

Type: boolean

Default: undefined

Determines whether a module that is refreshed will self-accept.

  • If acceptSelf is not provided or true, the refreshed module will be self-accept()ed.
  • If acceptSelf is false, the refreshed module will not be self-accept()ed and HMR invalidation will propagate up through the module tree. (NOTE: this is targeted for ADVANCED use cases.)

New test

const getSandbox = require('../helpers/sandbox');

it('allows self invalidation', async () => {
  //                                   ***NOTE***: I enhanced your test sandbox to allow this
  const [session] = await getSandbox({ pluginOptions: { acceptSelf: false } });

  await session.write('index.js', `module.exports = function Noop() { return null; };`);
  await session.reload();

  await session.write(
    'foo.js',
    `
      require('./bar');
      module.exports = function Foo() {};
      module.hot.accept('./bar', () => window.log('accept ./bar'));
    `
  );
  await session.write(
    'bar.js',
    `window.log('init BarV1'); module.exports = function Bar() { return null; };`
  );
  await session.patch(
    'index.js',
    `require('./foo'); module.exports = function Noop() { return null; };`
  );
  // Edited Bar doesn't self-accept
  session.resetState();
  await session.patch(
    'bar.js',
    `window.log('init BarV2'); module.exports = function Bar() { return null; };`
  );
  expect(session.logs).toStrictEqual(['accept ./bar']);
});

NOTES

I made a couple small changes to the test sandbox to allow passing plugin options. This was necessary to build the test correctly.

cha0s avatar Feb 21 '24 05:02 cha0s

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders
Open Preview

codesandbox[bot] avatar Feb 21 '24 05:02 codesandbox[bot]

FYI, fails are only because of things choking on Node 14

cha0s avatar Feb 27 '24 03:02 cha0s

I'm not sure this is what should be implemented - in particular by prohibiting custom accept chains we're also enforcing a constraint that modules cannot have side-effects which is essential for react-refresh to work properly. This also means things like the Error Overlay won't work reliably.

pmmmwh avatar Apr 25 '24 07:04 pmmmwh

Do you have a more concrete use case why this is needed, or a reproduction if you ran into something (which could be a bug)?

pmmmwh avatar Apr 25 '24 07:04 pmmmwh

Everything I posted here works fine.

I demonstrated a usecase/bug in my original comment.

I moved away from webpack; it's old and the ecosystem is RIP (case in point).

cha0s avatar May 05 '24 19:05 cha0s