Webpack 5 module federation missing chunk when dynamically loading remote module
Asked Answered
M

2

10

I am trying to split a big monolith React app into micro-frontends using webpack module federation. I already have the remote module deployed and working great when running the dev server locally, everything works perfectly fine. But after running yarn build and deploying the consumer app, it fails with the following error:

Uncaught (in promise) ChunkLoadError: Loading chunk 935 failed.

When commenting out the lazy loaded remote module, everything works fine.

Anyone have any idea how to get webpack to build correctly with a remotely loaded module?

This is my consumer component:

import WorkOrderTrackingErrorBoundary from './work_order_tracking_error_boundary'
const WorkOrderTracking = lazy(() => import('dMove/WorkOrderTracking'))

const WorkOrderTrackingPage = (props) => {
    const spinner = (
        <SpinnerContainer>
            <Spinner type="bars" color="#3e145e" width="48px"/>
        </SpinnerContainer>
    )


    return (
        <Suspense fallback={spinner}>
            <WorkOrderTrackingErrorBoundary>
                <WorkOrderTracking/>
            </WorkOrderTrackingErrorBoundary>
        </Suspense>
    );
};

export default WorkOrderTrackingPage;

and this is my webpack.prod.config.js file:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const StringReplacePlugin = require('string-replace-webpack-plugin');
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin")
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

const paths = require('./paths');
const path = require('path');

module.exports = {
    bail: true,
    mode: 'production',
    devtool: 'source-map',
    module: {
        rules: [
            {
                oneOf: [
                    {
                        test: /\.(js|jsx)$/,
                        exclude: paths.appNodeModules,
                        use: {
                            loader: 'babel-loader'
                        }
                    },
                    {
                        test: /\.html$/,
                        use: [
                            {
                                loader: 'html-loader'
                            }
                        ]
                    },
                    {
                        test: [
                            /\.bmp$/,
                            /\.gif$/,
                            /\.jpe?g$/,
                            /\.png$/,
                            /\.svg$/
                        ],
                        loader: require.resolve('url-loader'),
                        options: {
                            limit: 10000,
                            name: 'static/media/[name].[hash:8].[ext]'
                        }
                    },
                    {
                        exclude: [paths.appResources, paths.appNodeModules],
                        test: /\.css$/,
                        use: [
                            require.resolve('style-loader'),
                            {
                                loader: require.resolve('css-loader'),
                                options: {
                                    importLoaders: 1,
                                    modules: true,
                                    localIdentName: '[name]__[local]___[hash:base64:5]'
                                }
                            }
                        ]
                    },
                    {
                        include: [paths.appResources, paths.appNodeModules],
                        test: /\.css$/,
                        use: [
                            {
                                loader: 'style-loader' // creates style nodes from JS strings
                            },
                            {
                                loader: 'css-loader' // translates CSS into CommonJS
                            }
                        ]
                    },
                    {
                        exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
                        loader: require.resolve('file-loader'),
                        options: {
                            name: 'static/media/[name].[hash:8].[ext]'
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new ModuleFederationPlugin({
            name: "dView", // name of this project
            filename: "remoteEntry.js", // entry file to this project, remoteEntry.js is the conventional name
            exposes: {
                // components exposed out for other projects to consume
            },
            remotes: {
                // projects to consume
                dMove: "dMove@https://dmove-dev.3dsignals.io/remoteEntry.js"
            },
            shared: {
                // shared modules between projects
                react: {singleton: true},
                "react-dom": {singleton: true}
            }
        }),
        new webpack.DefinePlugin({
            "GET_API_TOKEN_URL": JSON.stringify("/security/getAPIToken"),
            "GET_REFRESH_TOKEN_URL": JSON.stringify("/security/refreshToken"),
            "LOGIN_WITH_FOREIGN_IDM": JSON.stringify("/security/foreignLogin"),
            "MAPBOX_API_TOKEN": JSON.stringify("pk.eyJ1IjoiM2RzZGV2b3BzIiwiYSI6ImNrazB2ZHJ2bTBsOTMydnJ1cG40dXc2NjYifQ.pGAk7cQ-ekRJY9KSSrW3lg")
        }),
        new StringReplacePlugin(),
        new HtmlWebpackPlugin({
            inject: true,
            template: paths.appHtml,
            minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeRedundantAttributes: true,
                useShortDoctype: true,
                removeEmptyAttributes: true,
                removeStyleLinkTypeAttributes: true,
                keepClosingSlash: true,
                minifyJS: true,
                minifyCSS: true,
                minifyURLs: true
            }
        }),
        new NodePolyfillPlugin()
    ],
    output: {
        filename: 'static/js/bundle.js',
        chunkFilename: 'static/js/[name].[contenthash].chunk.js',
        publicPath: '/',
        path: paths.appBuild,
        devtoolModuleFilenameTemplate: info =>
            path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')
    },
    entry: [
        '@babel/polyfill',
        paths.appIndexJs
    ],
    resolve: {
        modules: [
            'node_modules',
            paths.appNodeModules,
            paths.appSrc,
            paths.appResources
        ],
        extensions: [
            '.web.js',
            '.mjs',
            '.js',
            '.json',
            '.web.jsx',
            '.jsx',
            'tsx',
            'ts'
        ]
    }
};
Metabolism answered 31/3, 2021 at 12:50 Comment(1)
were you able to fix this issue, i am also facing the same issueBickel
C
0

ChunkLoadError: Loading chunk 946 failed. (missing:

I was also facing the missing - Uncaught (in promise) ChunkLoadError: Loading chunk 935 failed when access the chunks of another remote entry js and other chunks in development server but it's working fine local environment.

This chunk file was loading but content type showing as text/html instead of application/javascript when file loads in network tab response header.

enter image description here

You can check the response header - content type as application/javascript..If no then try to check nginx web server config for MIME types.

For me, it worked well in UAT but SIT was showing this error.

Hopes this will help to understand the reason of chunk files missing error.

Catlin answered 25/5, 2022 at 7:52 Comment(0)
A
0

In your webpack.prod.config.js file, eagerly load all of your shared libaries -

const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const StringReplacePlugin = require('string-replace-webpack-plugin');
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin")
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

const paths = require('./paths');
const path = require('path');

//DO THIS
const packageJson = require("../package.json");
const eagerlyLoadedDependencyObject = Object.keys(
  packageJson.dependencies
).reduce((obj, val) => {
  obj[val] = {
    eager: true,
  };
  return obj;
}, {});

module.exports = {
    bail: true,
    mode: 'production',
    devtool: 'source-map',
    module: {
        rules: [
            {
                oneOf: [
                    {
                        test: /\.(js|jsx)$/,
                        exclude: paths.appNodeModules,
                        use: {
                            loader: 'babel-loader'
                        }
                    },
                    {
                        test: /\.html$/,
                        use: [
                            {
                                loader: 'html-loader'
                            }
                        ]
                    },
                    {
                        test: [
                            /\.bmp$/,
                            /\.gif$/,
                            /\.jpe?g$/,
                            /\.png$/,
                            /\.svg$/
                        ],
                        loader: require.resolve('url-loader'),
                        options: {
                            limit: 10000,
                            name: 'static/media/[name].[hash:8].[ext]'
                        }
                    },
                    {
                        exclude: [paths.appResources, paths.appNodeModules],
                        test: /\.css$/,
                        use: [
                            require.resolve('style-loader'),
                            {
                                loader: require.resolve('css-loader'),
                                options: {
                                    importLoaders: 1,
                                    modules: true,
                                    localIdentName: '[name]__[local]___[hash:base64:5]'
                                }
                            }
                        ]
                    },
                    {
                        include: [paths.appResources, paths.appNodeModules],
                        test: /\.css$/,
                        use: [
                            {
                                loader: 'style-loader' // creates style nodes from JS strings
                            },
                            {
                                loader: 'css-loader' // translates CSS into CommonJS
                            }
                        ]
                    },
                    {
                        exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
                        loader: require.resolve('file-loader'),
                        options: {
                            name: 'static/media/[name].[hash:8].[ext]'
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new ModuleFederationPlugin({
            name: "dView", // name of this project
            filename: "remoteEntry.js", // entry file to this project, remoteEntry.js is the conventional name
            exposes: {
                // components exposed out for other projects to consume
            },
            remotes: {
                // projects to consume
                dMove: "dMove@https://dmove-dev.3dsignals.io/remoteEntry.js"
            },
            shared: packageJson.dependencies,
        }),
        new webpack.DefinePlugin({
            "GET_API_TOKEN_URL": JSON.stringify("/security/getAPIToken"),
            "GET_REFRESH_TOKEN_URL": JSON.stringify("/security/refreshToken"),
            "LOGIN_WITH_FOREIGN_IDM": JSON.stringify("/security/foreignLogin"),
            "MAPBOX_API_TOKEN": JSON.stringify("pk.eyJ1IjoiM2RzZGV2b3BzIiwiYSI6ImNrazB2ZHJ2bTBsOTMydnJ1cG40dXc2NjYifQ.pGAk7cQ-ekRJY9KSSrW3lg")
        }),
        new StringReplacePlugin(),
        new HtmlWebpackPlugin({
            inject: true,
            template: paths.appHtml,
            minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeRedundantAttributes: true,
                useShortDoctype: true,
                removeEmptyAttributes: true,
                removeStyleLinkTypeAttributes: true,
                keepClosingSlash: true,
                minifyJS: true,
                minifyCSS: true,
                minifyURLs: true
            }
        }),
        new NodePolyfillPlugin()
    ],
    output: {
        filename: 'static/js/bundle.js',
        chunkFilename: 'static/js/[name].[contenthash].chunk.js',
        publicPath: '/',
        path: paths.appBuild,
        devtoolModuleFilenameTemplate: info =>
            path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')
    },
    entry: [
        '@babel/polyfill',
        paths.appIndexJs
    ],
    resolve: {
        modules: [
            'node_modules',
            paths.appNodeModules,
            paths.appSrc,
            paths.appResources
        ],
        extensions: [
            '.web.js',
            '.mjs',
            '.js',
            '.json',
            '.web.jsx',
            '.jsx',
            'tsx',
            'ts'
        ]
    }
};

eagerlyLoadedDependencyObject changes your dependencies from -

{
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "react-redux": "7.2.6",
    "react-router-dom": "5.3.4"
}

to this -

{
    "react":
    {
        "eager": true
    },
    "react-dom":
    {
        "eager": true
    },
    "react-redux":
    {
        "eager": true
    },
    "react-router-dom":
    {
        "eager": true
    }
}
Allahabad answered 3/2, 2023 at 17:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.