I have an angular shell application and a react micro front end (MFE) that uses webpack and module federation. I have a problem with the remote entry pathing. When I set the MFE webpack output config like this:
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: 'auto',
},
And the shell like this:
loadRemoteModule({
remoteEntry: "http://localhost:3000/remoteEntry.js",
remoteName: "prompt_mfe",
exposedModule: "./Prompt",
});
It works fine. However, I need the MFE to be served under the /mfe-prompt/ path. But if I configure it like this:
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/mfe-prompt/',
},
And the shell like this:
loadRemoteModule({
remoteEntry: "http://localhost:3000/mfe-prompt/remoteEntry.js",
remoteName: "prompt_mfe",
exposedModule: "./Prompt",
});
It does not import the MFE properly. Instead, I see the following error on the network tab:
GET http://localhost:4201/mfe-prompt/src_containers_Prompt_Prompt_tsx.bundle.js net::ERR_ABORTED 404 (Not Found)
I don't understand why, when I set the public path, the file becomes src_containers_Prompt_Prompt_tsx.bundle.js instead of remoteEntry.js, and I also don't understand the 404. If I go to that GET path in the browser, it exists and I can see the JS content. Is there further configuration I need to do when I set the public path? Something missing? Below are my complete webpack files:
Shell application:
import { container } from 'webpack';
const deps = require('./package.json').dependencies;
module.exports = {
output: {
publicPath: 'http://localhost:4201/',
uniqueName: 'musicapp',
scriptType: 'text/javascript',
},
optimization: {
runtimeChunk: false,
},
devServer: {
port: 4201,
headers: {
'Access-Control-Allow-Origin': '*',
},
hot: true,
},
plugins: [
new container.ModuleFederationPlugin({
name: 'angular-shell',
filename: 'remoteEntry.js',
remotes: {
prompt_mfe: `prompt_mfe@http://localhost:3000/mfe-prompt/remoteEntry.js`,
},
shared: {
react: { singleton: true, eager: true, requiredVersion: deps.react },
'react-dom': {
singleton: true,
eager: true,
requiredVersion: deps['react-dom'],
},
'react-router-dom': {
singleton: true,
eager: true,
requiredVersion: deps['react-router-dom'],
},
'@dds/react': {
singleton: true,
eager: true,
requiredVersion: deps['@dds/react'],
},
'prop-types': {
singleton: true,
eager: true,
requiredVersion: deps['prop-types'],
},
},
}),
],
};
MFE:
const HtmlWebPackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');
const deps = require('./package.json').dependencies;
const htmlPlugin = new HtmlWebPackPlugin({
template: './public/index.html',
filename: './index.html',
publicPath: '/mfe-prompt/'
});
module.exports = {
mode: 'development',
entry: './src/index.tsx',
resolve: {
extensions: ['.ts', '.tsx', '.js'],
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/mfe-prompt/',
},
devServer: {
static: path.join(__dirname, 'dist'),
port: 3000,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
module: {
rules: [
{
test: /\.(ts|tsx|js)$/,
exclude: /node_modules/,
loader: 'ts-loader',
},
{
test: /\.s[ac]ss$/i,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
],
},
plugins: [
htmlPlugin,
new ModuleFederationPlugin({
name: 'prompt_mfe',
filename: 'remoteEntry.js',
exposes: {
'./Prompt': './src/containers/Prompt/Prompt.tsx',
},
shared: {
react: { singleton: true, eager: true, requiredVersion: deps.react },
'react-dom': {
singleton: true,
eager: true,
requiredVersion: deps['react-dom'],
},
'react-router-dom': {
singleton: true,
eager: true,
requiredVersion: deps['react-router-dom'],
},
'@dds/react': {
singleton: true,
eager: true,
requiredVersion: deps['@dds/react'],
},
'prop-types': {
singleton: true,
eager: true,
requiredVersion: deps['prop-types'],
},
},
}),
],
};