I am trying to implement Micro Frontends using Angular 13 and React, using Webpack 5 and the Module Federation plugin. I have a remote Angular application that I am trying to import into my host React application. I have followed the documentation and guides, but I am getting an error ScriptExternalLoadError: Loading script failed. when trying to load the remote Angular application.
Here are the details of my implementation:
- I have two separate applications, one Angular and one React.
- In my Angular application, I have added the @module-federation/client package to my dependencies and configured the webpack.config.js file to expose the required modules.
webpack-config Angular Application
const path = require('path');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: './src/bootstrap.ts',
optimization: {
splitChunks: false,
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
chunkFilename: '[id].chunk.js',
scriptType: 'text/javascript'
},
resolve: {
extensions: ['.ts', '.js'],
},
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
},
},
],
},
{
test: /\.html$/,
loader: 'html-loader',
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
compress: true,
port: 8082,
hot: true,
historyApiFallback: true,
},
plugins : [
new ModuleFederationPlugin({
name : 'cubs',
library: { type: 'var', name: 'profile' },
filename : 'remoteEntry.js',
exposes : {
'./angular' : './src/bootstrap'
}
}),
]
};
package-json
{
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular-builders/custom-webpack": "^13.1.0",
"@angular/animations": "~13.3.0",
"@angular/common": "~13.3.0",
"@angular/compiler": "~13.3.0",
"@angular/core": "~13.3.0",
"@angular/forms": "~13.3.0",
"@angular/platform-browser": "~13.3.0",
"@angular/platform-browser-dynamic": "~13.3.0",
"@angular/router": "~13.3.0",
"clean-webpack-plugin": "^4.0.0",
"html-webpack-plugin": "^5.5.1",
"i": "^0.3.7",
"npm": "^9.6.6",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~13.3.11",
"@angular/cli": "~13.3.11",
"@angular/compiler-cli": "~13.3.0",
"@types/jasmine": "~3.10.0",
"@types/node": "^12.11.1",
"jasmine-core": "~4.0.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.1.0",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"style-loader": "^3.3.2",
"ts-loader": "^9.4.2",
"typescript": "~4.6.2"
}
}
angular.json
{
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./custom-webpack.config.js"
},
bootstrap.ts
import 'zone.js';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { AppComponent } from './app/app.component';
export function mount(container: HTMLElement, props: any) {
const appComponent = new AppComponent();
const element = appComponent.getElement();
container.appendChild(element);
console.log("Component mounted")
}
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
In my React host Application webpack-dev.config
const { merge } = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const commonConfig = require('./webpack.common')
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
const devConfig = {
mode : 'development',
devServer : {
port : 8080,
historyApiFallback : {
index : 'index.html'
}
},
plugins : [
new ModuleFederationPlugin({
name : 'container',
remotes : {
reactapp : 'reactapp@http://localhost:8081/remoteEntry.js',
angular : "angular@http://localhost:8082/remoteEntry.js"
}
}),
new HtmlWebpackPlugin({
template : '/public/index.html'
})
]
}
module.exports = merge(commonConfig, devConfig)
React Component thats imported in the App.js
import React, { useRef, useEffect } from 'react';
import 'angular/angular'
const AngularAppWrapper = () => {
const appRef = useRef(null);
console.log("Making an attempt")
console.log(mount);
useEffect(() => {
if (appRef.current) {
mount(appRef.current, {});
}
}, []);
return <div ref={appRef} />;
};
export default AngularAppWrapper;
Host react Bootstrap
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
- I have verified that the remoteEntry.js file is being served from the Angular application at the correct URL (http://localhost:8082/remoteEntry.js).
- I have tried several solutions suggested online, such as clearing the cache, using incognito mode, checking the network logs, and checking the path of the imported modules.
- Despite all this, I am still getting the error. I am not sure what else to try. Can someone please help me diagnose and fix this issue?