javascriptcode-splittingpreact

How can I do manual code-splitting with preact?


I want to do code-splitting manually using preact. Preact already splits code for routes, but I want to do it myself.

My use case is that I am building a tool where a user can add widgets to a dashboard. On the home page I only want to include the code for the widgets that the user has configured, not the ones the user has not used.

So I do not want to have the code for all widgets bundled in the bundle.js, but request it lazily when needed, when rendering the list of widgets.

I have attempted to use the async! syntax, which I saw in some old commits for the boiler plate, but that did not work.

A simplified example of my code

The configuration data

[{ "type": "notes", "title": "Widget 1}, { "type": "todo", "title": "Widget 2"}]

The render function of the list

const Grid = ({ widgets }) => (
    <ul>
        {widgets.map((widget) => <li key={widget.title}><Widget widget={widget} /></li>)}
    </ul>
);

Widget component

Here I have a mapping from type to component:

import notes from widgets/notes;
import todo from widgets/todo;

class Widget extends Component {
    widgetMap(widget) {
      if (widget.type === 'notes') {
         return notes;
      }
      if (widget.type === 'todo') {
          return todo;
      }
    }

    render ({ widget }) {
        const widgetComponent = this.widgetMap(map);
        return (
            <div>
                <h1>{widget.title}</h1>
                <widgetComponent />
            </div>
        );
    } 
}


Solution

  • If you are using Preact X, it features <Suspense> and lazy which is same API React also uses. More about it in depth you can read here: https://reactjs.org/docs/concurrent-mode-suspense.html

    Your example, modified would look like this (code adjusted from here):

    import { Suspense, lazy } from `preact/compat`;
    
    const notes = lazy(() => import('./widgets/notes'));
    const todo = lazy(() => import('./widgets/todo'));
    
    class Widget extends Component {
        widgetMap(widget) {
          if (widget.type === 'notes') {
             return notes;
          }
          if (widget.type === 'todo') {
              return todo;
          }
        }
    
        render ({ widget }) {
            const widgetComponent = this.widgetMap(map);
            return (
                <Suspense fallback={<div>loading...</div>}>
                    <div>
                        <h1>{widget.title}</h1>
                        <widgetComponent />
                    </div>
                </Suspense>
            );
        } 
    }
    

    For older version of Preact, you can put together async loading HOC yourself as long as you have Babel or some other transpiler set up to handle dynamic module loading

    export default asyncComponent = (importComponent) => {
      class AsyncComponent extends Component {
        constructor(props) {
          super(props);
          this.state = { component: null };
        }
    
        async componentDidMount() {
          const { default: component } = await importComponent();
          this.setState({ component });
        }
    
        render() {
          const Component = this.state.component;
          return Component ? <Component {...this.props} /> : <div>loading...</div>;
        }
      }
    
      return AsyncComponent;
    }