4

I saw this answer which shows how to get react-hot-loader working with import() syntax, but in my case I don't know the filename until runtime.

Here's what I've got:

export default function(component, props, mountPoint) {

    function render() {
        import(`./containers/${component}`).then(({default: Component}) => {
            ReactDOM.render(
                <AppContainer>
                    <ErrorBoundary>
                        <Component {...props}/>
                    </ErrorBoundary>
                </AppContainer>, document.getElementById(mountPoint || 'react-root'));
        });
    }

    render();

    if(module.hot) {
        module.hot.accept('./containers', () => {
            render();
        });
    }

}

The first load works fine, it's just the module.hot block that doesn't work. Chrome tells me:

Uncaught (in promise) Error: Cannot find module "./containers"

And my terminal tells me the same thing:

WARNING in ./node_modules/babel-loader/lib?{"cacheDirectory":"/usr/local/myproject/cache/babel","forceEnv":"development"}!./assets/scripts/app/react_loader.js Module not found: Error: Can't resolve './containers' in '/usr/local/myproject/assets/scripts/app'

If I try to accept ./containers/${component} then I get a runtime error instead:

Ignored an update to unaccepted module ./assets/scripts/lib/components/bpm/MyClientProcessMenu.jsx -> ./assets/scripts/lib/components/bpm/MyClientProcessMenuLoader.jsx -> ./assets/scripts/app/containers/MyClientProcessMenuContainer.jsx -> ./assets/scripts/app/containers lazy recursive ^./.$ -> ./node_modules/babel-loader/lib/index.js?{"cacheDirectory":"/usr/local/myproject/cache/babel","forceEnv":"development"}!./assets/scripts/app/react_loader.js -> ./node_modules/bundle-loader/index.js!./assets/scripts/app/react_loader.js -> ./assets/scripts/app recursive ./node_modules/bundle-loader/index.js!./ ^./.$ -> ./assets/scripts/lib/webpack.js -> ./assets/main.js -> 0

And no update occurs.

How can I "accept" a dynamic component?

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • Can you please share your `.babelrc` and `webpack.config.js` ? – Aaqib Feb 23 '18 at 21:13
  • @Aaqib Sure, here: https://gist.github.com/mnpenner/298208c1a3e6151fc36858d306c1dda6 – mpen Feb 23 '18 at 22:06
  • Try installing `babel-plugin-syntax-dynamic-import` and give to the plugins array inside `.babelrc` https://www.npmjs.com/package/babel-plugin-syntax-dynamic-import – Aaqib Feb 23 '18 at 22:17
  • @Aaqib I already have it: https://gist.github.com/mnpenner/298208c1a3e6151fc36858d306c1dda6#file-babelrc-L41 – mpen Feb 23 '18 at 22:18
  • Please add `react-hot-loader/babel` inside `.babelrc` plugins array https://github.com/gaearon/react-hot-loader – Aaqib Feb 23 '18 at 22:29
  • @Aaqib Added it. No change. I think you're missing the heart of the question. I've gotten `react-hot-loader` working in the past, but never with dynamic `import()` and dynamic filenames. That's the part I want to know how to do. – mpen Feb 23 '18 at 22:37

1 Answers1

1

I do not think that this is currently supported without duplicating a code. As a workaround, you can create two files one would be used for production with dynamic import and the second one would be without dynamic import for development.

The file with dynamic import has to be included only in production. That's the reason for moving the environment logic into the different file (index.js)

index.js

// Neded because HMR doesn't work with dynamic import for languages
let app;
if (process.env.NODE_ENV === 'development') {
  app = require('./development').default;
} else {
  app = require('./production').default;
}
app(component);

client.js

export default function(Component) {

    function render() {
       ReactDOM.render(
         <AppContainer>
           <Component />
         </AppContainer>, document.getElementById('react-root'));
    }

    render();

    if(module.hot) {
        module.hot.accept('./containers', () => {
            render();
        });
    }
}

production.js

import client from './client';


export default function (component) {
  import(`./containers/${component}`).then(Component => {
    client(Component)
  });
}

development.js

import client from './client';

export default function (component) {
  const Component = require(`./containers/${component}`);

  client(Component);
}
Black
  • 9,541
  • 3
  • 54
  • 54
  • I don't think I'm allowed to accept `'./containers'` -- I get an error: "Can't resolve './containers'" – mpen Jul 18 '18 at 23:32
  • @mpen is your path to containers correct? Looks like to be problem only with path. – Black Jul 19 '18 at 21:12
  • Yes, it's correct. [The docs](https://webpack.js.org/api/hot-module-replacement/#accept) don't mention being able to accept a directory. – mpen Jul 19 '18 at 21:21
  • yes, you have to specify main file for me it is Layout file with routes ```if (module.hot) { module.hot.accept('modules/layout', () => { const newLayout = require('modules/layout').default; renderApp(newLayout); }); }``` Layout: ``` {routes.map(route => ( } /> ))} ``` plus some layout – Black Jul 20 '18 at 09:45
  • I do not think that this is related to dynamic imports. Was this really working before you have introduced dynamic imports into your code? – Black Jul 20 '18 at 09:48
  • Which? Hot-reloading? I had dynamic requires *and* hot-reloading working via `require.context`. Here's the [gist](https://gist.github.com/mnpenner/ef67aa6d76899e0c883e5b80d3967891). It requires you pass `context.id` to `module.hot.accept`. I can't find an equivalent concept for `import()`. This method stopped working in Webpack 4 in IE11 though, not sure why. I think it's a bug in WP. I'm using your approach now and split dev+prod loaders, because `import()` works in IE, and then using `require.context` on dev. It works, but I'd like to converge the implementations if I can. – mpen Jul 20 '18 at 19:50