In Webpack 4, can we dynamically generate page chunk with import() token so we can turn a react component into a react-loadable one?
Asked Answered
G

1

7

We use react and react-loadable.

During our application initialization, we are verifying that the component.preload method is existing for each <Route /> we define.

If the method is missing, we display a warning that show that the component should be loadable.

We use webpack 4, is there a way to automatically wrap the component, so we don't have to do it manually?

This is how a component look like:

/** MyComponent.js: page component */
export default () => <div>Hello world</div>;

This is the same component wrapped in a react-loadable component:

/**
 * preconfigured react-loadable 
 * See https://github.com/jamiebuilds/react-loadable#how-do-i-avoid-repetition)
 */
import MyLoadable from '@scopped/react-loadable';

/** loadable component */
export default MyLoadable({
  loader: () => import('./MyComponent'), /** import page component */
});
  1. Our <Route /> are declared in node_modules and from within different packages.
  2. It may be declared using <Resource /> (from react-admin) instead of <Route />
  3. They are not distributed in ESM format but only CJS (CommonJS).
Glowworm answered 25/2, 2019 at 10:23 Comment(1)
You could write your own webpack loader. I'm not aware of any existing one that can help with this. webpack.js.org/contribute/writing-a-loaderHeredia
C
3

I'm not sure this is the right way to do it but maybe you can write some kind of webpack loader that would preprocess your files, find <Route /> patterns in your files, identify the path of the components they render and transform them into loadable components with that information.

This is a bit hacky but it should work (Only with imports but you can tweak it as you want to match your requirements):

Webpack config:

{
  test: /\.js$/,
  exclude: /node_modules/,
  use: {
    loader: [
      "babel-loader", // Rest of your loaders
      path.resolve(__dirname, 'path/to/your/loader.js')
    ]
  }
}

loader.js:

module.exports = function (source) {
  const routeRegex = new RegExp(/<Route.*component={(.*)}.*\/>/g);
  let matches;
  let components = [];

  while (matches = routeRegex.exec(source)) {
    components.push(matches[1]); // Get all the component import names
  }

  // Replace all import lines by a MyLoadable lines
  components.forEach((component) => {
    const importRegex = new RegExp(`import ${component} from '(.*)'`);
    const path = importRegex.exec(source)[1];

    source = source.replace(importRegex, `
      const ${component} = MyLoadable({
        loader: () => import('${path}')
      });
    `);
  });

  source = `
    import MyLoadable from './MyLoadable';
    ${source}
  `;

  return source;
};

This is definitely hacky but if you stick to convention this could work. It transforms this kind of file:

import Page1 from './Page1';
import Page2 from './Page2';

export default () => (
  <Switch>
    <Route path='/page1' component={Page1} />
    <Route path='/page2' component={Page2} />
  </Switch>
);

into this file:

import MyLoadable from './MyLoadable;

const Page1 = MyLoadable({
  loader: () => import('./Page1')
});

const Page2 = MyLoadable({
  loader: () => import('./Page2')
});

export default () => (
  <Switch>
    <Route path='/page1' component={Page1} />
    <Route path='/page2' component={Page2} />
  </Switch>
);

This example has some problems (the path to MyLoadable should be absolute, it works only when Page components are imported, loadable components are not in a separate file and this could lead to duplicates, ...) but you get the idea

Conquian answered 25/2, 2019 at 13:18 Comment(1)
Thanks a lot for that answer, I can see the big picture and it sounds a bit complex to handle. I can more complexity when solved like this because: (1) Our <Route /> are declared in node_modules and from within different packages. (2) It use <Resource /> (from react-admin), (3) it is not distributed in ESM format but CJS. (4) We want to do that only if it's not a loadable component. One good point in all that is that our MyLoadable is already in it's own package.Glowworm

© 2022 - 2024 — McMap. All rights reserved.