How can I 'bootstrap' support for webpack Module Federation when the webpack output type is "library"?

Viewed 577

I have a webpack 'library' project with a webpack config that looks like this:

module.exports = ({ mode }) => {
    const isProd = mode === 'production';
    return {
        mode,
        entry: './src/index.tsx',
        output: {
            library: 'MyLibraryComponents',
            path: path.resolve(__dirname, 'build'),
            filename: getOutputFileName(isProd),
        },
        devServer: { ... },
        optimization: { ... },
        module: {
            rules: [
                {
                    test: /\.tsx?$/,
                    loader: 'ts-loader',
                },
                {
                    test: /\.less$/i,
                    use: [
                        { loader: 'style-loader' },
                        { loader: 'css-loader' },
                        {
                            loader: 'less-loader',
                        },
                    ],
                },
                {
                    test: /\.css$/i,
                    use: ['style-loader', 'css-loader'],
                },
            ],
        },
        resolve: {
            extensions: ['.ts', '.tsx', '.js'],
        },
        plugins: [
            new HtmlWebpackPlugin({
                inject: true,
                scriptLoading: 'blocking',
                template: resolveAppPath('public/index.html'),
            }),
        ],
    };
};

This webpack build creates a global variable called "MyLibraryComponents", which has methods on it that render react components given an HTMLElement using ReactDOM.render(). My project is set up this way because I'm working in a legacy application, but want to re-write certain parts of the UI in react.

Here is what the entry file for the library looks like. The entry file is in .ts but I've simplified it a bit here:

// src/index.tsx

import ReactDOM from 'react-dom';

export Examples = {
   FooComponent: {
       async render(htmlElement, props) {
           const { default: Foo } = await import("./components/Foo");
           ReactDOM.render(<Foo {...props} />, htmlElement);
       }
   },
   BarComponent: {
       async render(htmlElement, props) {
           const { default: Bar } = await import("./components/Bar");
           ReactDOM.render(<Bar {...props} />, htmlElement);
       }
   }
};

When the webpack main bundle is downloaded using a <script src=''>, it creates an object on window called MyLibraryComponents. I can then render react components into the legacy app like this: MyLibraryComponents.Examples.FooComponent.render(...)

As more components are added to this library, I'm interested in potentially using Module Federation as a means to independently deploy components, and to isolate dependencies, etc.

However, in all the module federation project examples I've seen it is necessary to have a "bootstrap" file and have your webpack entry file do this. And then your "real" entry file to your app should be renamed to bootstrap.js:

import("./bootstrap")

Apparently, this ensures that the project's shared modules are loaded by the browser first, or something like that. The reason isn't 100% clear to me yet.

The issue I'm seeing is that my project's entry file actually has "exports" that my webpack library depends on, whereas if this was a traditional react app, it would just do something like this:

import ReactDOM from "react-dom";
import App from "./App;

ReactDOM.render(<App />, document.getElementById("root"));

If I update my entry point file to just import('./bootstrap'), then my library just ends up being an empty object when I inspect it in the console.

Is there a way I can write the 'bootstrap' entry file so that I can support module federation in this project?

Thank you for the help.

1 Answers

I needed to update my new index file to do something like this:

// index entry file

import("./bootstrap").then(MyLibraryComponents => {
      window.MyLibraryComponents = MyLibraryComponents;
});

Any code using referencing window.MyLibraryComponents needs to ensure this exists now though since it's now being "loaded" asynchronously. Or you can do something like this:

setTimeout(() => {
    // Do something with window.MyLibraryComponents...
}, 1000);
Related