Tuesday, 15 September 2020

Loading webpack bundle via script tag for microfrontend approach

I'm following a couple articles on how to implement a simple micro-frontend approach with React (here and here, see sample repos at bottom of question).

It works perfectly when the two apps (the root app, and the subapp) are running in their respective developoment servers. However, when I deploy the build artifacts to the real web server, it doesn't work. This is the important code in the root app:

function MicroFrontend({name, host, history}) {

    useEffect(() => {
        const scriptId = `micro-frontend-script-${name}`;

        const renderMicroFrontend = () => {
            window["render" + name](name + "-container", history);
        };

        if (document.getElementById(scriptId)) {
            renderMicroFrontend();
            return;
        }

        fetch(`${host}/asset-manifest.json`)
            .then((res) => res.json())
            .then((manifest) => {
                const script = document.createElement("script");
                script.id = scriptId;
                script.crossOrigin = "";
                script.src = `${host}${manifest.files["main.js"]}`;
                script.onload = () => {
                    renderMicroFrontend();
                };
                document.head.appendChild(script);
            });

        return () => {
            window[`unmount${name}`] && window[`unmount${name}`](`${name}-container`);
        };
    });

    return <main id={`${name}-container`}/>;
}

MicroFrontend.defaultProps = {
    document,
    window,
};

When I click on the button that loads the main.js for the micro-app, it works fine up till the point where it calls the renderMicroFrontend above. Then I get this error in my browser: Uncaught TypeError: window[("render" + t)] is not a function

This is because it can't find the function that loads the microfrontend that is supposed to be on window. When I run the two apps in the dev server, I have the correct function on window and it works. When I follow the same steps with the two apps deployed to a real server (after running npm run build), instead of having the renderMicroApp function on my window, I have a different variable called: webpackJsonpmicro-app.

I figured out that this is due to the output.library option (and/or related options) in webpack, from the webpack docs:

output.jsonpFunction. string = 'webpackJsonp' Only used when target is set to 'web', which uses JSONP for loading on-demand chunks. If using the output.library option, the library name is automatically concatenated with output.jsonpFunction's value.

I basically want the bundle/script to be loaded and evaluated, so that the renderMicroApp function is available on the window, but I'm lost regarding what webpack settings I need for this to work, since there are lots of different permutations of options.

For reference, I'm using the following config-overrides in the micro-app (atop react-app-rewired):

module.exports = {
  webpack: (config, env) => {
    config.optimization.runtimeChunk = false;
    config.optimization.splitChunks = {
      cacheGroups: {
        default: false,
      },
    };

    config.output.filename = "static/js/[name].js";

    config.plugins[5].options.filename = "static/css/[name].css";
    config.plugins[5].options.moduleFilename = () => "static/css/main.css";
    return config;
  },
};

And in my index.tsx (in the micro app) I'm exposing the function on the window:

window.renderMicroApp = (containerId, history) => {
  ReactDOM.render(<AppRoot />, document.getElementById(containerId));
  serviceWorker.unregister();
};

Some sample repos:



from Loading webpack bundle via script tag for microfrontend approach

No comments:

Post a Comment