简体   繁体   中英

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:

You could try with terser-webpack-plugin .

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  webpack: (config, env) => {
    config.optimization.minimize = true;
    config.optimization.minimizer = [new TerserPlugin({
      terserOptions: { keep_fnames: true }
    })];

    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;
  }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM