Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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).
like image 969
Dimitri Kopriwa Avatar asked Feb 25 '19 10:02

Dimitri Kopriwa


People also ask

How to solve the problem of dynamic loading files in Webpack?

To solve the problem of dynamic loading files, we can simply choose the loading strategy: This will force Webpack to include the file chunk inside the parent bundle/chunk, forcing it to not create a separated chunk for that. This way, all the file paths will be promptly available when your app loads the parent bundle/chunk.

How do I split a dynamic code in a Webpack package?

Two similar techniques are supported by webpack when it comes to dynamic code splitting. The first and recommended approach is to use the import () syntax that conforms to the ECMAScript proposal for dynamic imports. The legacy, webpack-specific approach is to use require.ensure. Let's try using the first of these two approaches...

What is webpackchunkname?

webpackChunkName: A name for the new chunk. Since webpack 2.6.0, the placeholders [index] and [request] are supported within the given string to an incremented number or the actual resolved filename respectively. You can use [request] placeholder to set dynamic chunk name. So the chunk name will be Cat.

Is it possible to provide a dynamic expression to import () in Webpack?

It is possible to provide a dynamic expression to import () when you might need to import specific module based on a computed variable later. Webpack 4.6.0+ adds support for prefetching and preloading. Using these inline directives while declaring your imports allows webpack to output “Resource Hint” which tells the browser that for:


1 Answers

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

like image 52
Saraband Avatar answered Oct 13 '22 00:10

Saraband