Webpack Module Federation fails with Next JS when using styled-components - reactjs

I'm trying to get Webpack Module Federation working with a NextJS app where the federated module is using styled-components. My configuration is working as expected if I'm only using react and react-dom but using styled-components gives me these errors:
It looks like there are several instances of 'styled-components' initialized in this application. This may cause dynamic styles to not render properly, errors during the rehydration process, a missing theme prop, and makes your application bigger without good reason.
and
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
I have tried the different approaches to resolve styled-components to a single instance by modifying the Webpack config in both the host and remote apps, but all seem to fail.
Here is my current configuration:
Federated modules repo (webpack.config.js):
const { ModuleFederationPlugin } = require("webpack").container;
const deps = require("./package.json").dependencies;
const path = require("path");
module.exports = {
mode: "development",
devServer: {
contentBase: path.join(__dirname, "dist"),
port: 3001,
},
output: {
publicPath: "auto",
},
resolve: {
extensions: [".js"],
},
module: {
rules: [
{
test: /\.js$/,
loader: "babel-loader",
exclude: /node_modules/,
options: {
plugins: [
["babel-plugin-styled-components", { pure: true, ssr: true }],
],
presets: ["#babel/preset-react"],
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "remoteLib",
filename: "remoteEntry.js",
exposes: {
"./Button": "./src/Button",
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
"styled-components": {
singleton: true,
requiredVersion: deps["styled-components"],
},
},
}),
],
};
Consuming NextJS app Webpack config (next.config.js):
webpack: (config, options) => {
const { ModuleFederationPlugin } = options.webpack.container;
config.plugins.push(
new ModuleFederationPlugin({
remotes: {
'federated-components': 'remoteLib#http://localhost:3001/remoteEntry.js'
}
})
);
return config;
}

Related

MFE module federation cannot find module for remote child import

Im trying to build out a micro frontend for the first time so my main application can support a sub application. I have it all working when everything is just rendering App.tsx (remote) but as soon as I try and render a child within the remote application I get the Cannot find module error from webpack
Container craco.congig.js
/* eslint-disable #typescript-eslint/no-var-requires */
const CracoEsbuildPlugin = require('craco-esbuild');
const { ModuleFederationPlugin } = require("webpack").container;
const deps = require("./package.json").dependencies;
module.exports = {
webpack: {
plugins: {
add: [
new ModuleFederationPlugin({
name: "Dashboard",
remotes: {
DigitalCanopy: "DigitalCanopy#//localhost:3001/remoteEntry.js",
},
shared: {
react: {
singleton: true,
strictVersion: true,
requiredVersion: deps['react']
},
"react-dom": {
singleton: true,
strictVersion: true,
requiredVersion: deps['react-dom']
},
},
}),
],
},
...
My remote craco.config.js
/* eslint-disable #typescript-eslint/no-var-requires */
const CracoEsbuildPlugin = require('craco-esbuild');
const { ModuleFederationPlugin } = require("webpack").container;
const deps = require("./package.json").dependencies;
module.exports = {
devServer: {
port: 3001
},
webpack: {
plugins: {
add: [
new ModuleFederationPlugin({
name: "DigitalCanopy",
exposes: {
"./DigitalCanopy": "./src/App.tsx",
},
filename: "remoteEntry.js",
shared: {
react: { singleton: true },
"react-dom": { singleton: true },
},
}),
],
},
configure: (webpackConfig) => ({
...webpackConfig,
output: {
...webpackConfig.output,
publicPath: "auto",
},
}),
},
plugins: [
{
plugin: CracoEsbuildPlugin,
options: {
esbuildLoaderOptions: {
// Optional. Defaults to auto-detect loader.
loader: 'tsx', // Set the value to 'tsx' if you use typescript
target: 'es2018',
},
esbuildMinimizerOptions: {
target: 'es2018',
css: true, // if true, OptimizeCssAssetsWebpackPlugin will also be replaced by esbuild.
},
skipEsbuildJest: false, // Optional. Set to true if you want to use babel for jest tests,
esbuildJestOptions: {
loaders: {
'.ts': 'ts',
'.tsx': 'tsx',
},
},
},
},
],
};
My remote App.tsx
import { createTheme, ThemeProvider } from '#mui/material';
import React from 'react';
import './App.css';
import App1 from './App1';
const baseTheme = createTheme({
...MUI THEME STUFF
});
function App() {
return (
<ThemeProvider theme={baseTheme}>
<div className="App">Digital Canopy App</div>;
<App1 />
</ThemeProvider>
);
}
export default App;
This works and renders fine until I try and render <App1 />
Then I get this error Module not found: Error: Can't resolve './App1' in ...
Any ideas? I feel like this should just work. Importing children components from within the remote is pretty standard doesnt seem unique but I cannot find anything similar online which makes me think Im missing something obvious.
Thanks for the help
My issue was I was missing the .tsx extension on the import. I have no idea why that is required by the sub application. My build configs are identical for typescript and my main app does not require that. I'll dig in and try and figure out why

SVGs and PNGs not loading

I am working on a micro frontend using Webpack.
And I have a problem where all local my SVGs and PNGs are not being loaded by Webpack5 react app. I keep getting 404 when doing that.
Can anyone point me out what I am doing wrong?
Here is the folder structuring
/public
/src
/components
navbar.tsx
/assets
Logo.svg
webpack.config.js
Here is my Webpack config. I am including the loader for assets, as indicated in the Webpack documentation
Webpack.config.js
const HtmlWebPackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const deps = require("./package.json").dependencies;
module.exports = {
output: {
publicPath: "http://localhost:3000/",
},
resolve: {
extensions: [".vue", ".tsx", ".ts", ".jsx", ".js", ".json"],
},
devServer: {
port: 3000,
historyApiFallback: true,
},
module: {
rules: [
{
test: /\.m?js/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
},
{
test: /\.(css|s[ac]ss)$/i,
use: ["style-loader", "css-loader", "postcss-loader"],
},
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: { loader: "babel-loader" },
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "App1",
filename: "remoteEntry.js",
remotes: {},
exposes: {},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
new HtmlWebPackPlugin({
template: "./src/index.html",
}),
],
};
And here is one of the images imported in the Navbar.
Navbar.tsx
import React from "react";
export default function Navbar() {
return (
<img src="./assets/Logo.svg" alt="Logo" />
)
}
I'm not an expert in react but with the app rendered in App.js maybe your url img src should be something like src='./components/assets/ because it would start from the src folder ? (i know if it's an import it works as expected but here it's a src ..)
Feel free to delete i didn't have enough karma to comment

React module federation micro frontend- webpack 5 public path auto is not working

In my react micro frontend project which uses module federation plugin remoteEntry.js path is incorrectly fetched and application crashes.
With hard-coded publicPath: "http://localhost:3000/" in webpack things work as expected.
But due to existing CI/CD setup in my project, publicPath in the webpack config cannot be hard-coded at the build time.
As per various articles it was recommended to use publicPath : auto in the webpack config of the application.
Yes publicPath : auto solves the issue of hard coding at build time.
But during page refresh, remoteEntry.js is fetched from incorrect url.
To simulate the scenario I created simple react application using webpack and module federation plugin
On the initial load remoteEntry.js is fetched from http://localhost:3000/remoteEntry.js as expected
When under nested URL http://localhost:3000/home/foo/about, On refresh main.js and remoteEntry.js is fetched from http://localhost:3000/home/foo/remoteEntry.js - application crashes
Github link for the project simulating the scenario
https://github.com/jidu0106/mf-page-refresh-issue
webpack.config.js
const HtmlWebPackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const deps = require("./package.json").dependencies;
module.exports = {
output: {
publicPath: "auto",
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
},
devServer: {
port: 3000,
historyApiFallback: true,
},
module: {
rules: [
{
test: /\.m?js/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
},
{
test: /\.(css|s[ac]ss)$/i,
use: ["style-loader", "css-loader", "postcss-loader"],
},
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "mf_page_refresh_issue",
filename: "remoteEntry.js",
remotes: {},
exposes: {
'./Component' : './src/test-component.js'
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
new HtmlWebPackPlugin({
template: "./src/index.html",
}),
],
};
I am using "react-router-dom": "^6.3.0" and also tried with "#reach/router": "^1.3.4", but the issue is same
Tried solutions given in https://github.com/module-federation/module-federation-examples/issues/102 but it didn't work
Last suggestion from ScriptedAlchemy in the above link is to use publicPath : auto in 2022
Any help would much appreciated. Thanks in advance.
In case anyone is facing the same issue, add another publicPath : '/' in HtmlWebpackPlugin().
something like the following
new HtmlWebPackPlugin({
template: './public/index.html',
favicon: './public/favicon.png',
assets: './public/assets',
publicPath: '/',
}),
Referred workaround from the following link and it helped
https://github.com/module-federation/module-federation-examples/pull/2170

How can I pull in an AngularJS app into a React host app Using Single-Spa

I'm hoping someone will be able to help.
I'm trying to use single-spa-angularjs to pull in a legacy AngularJS application into a React app. So the React app is the host. In the same React app, I'm also using Module Federation to pull in a separate React microfrontend.
So to sum it up, I have three apps:
React app - host and container
AngularJS app - legacy app using
AngularJS version 1.4.8
React microfrontend
All three apps are completely separate and hosted separately.
I was able to pull in the React microfrontend using Module Federation with no issues. The problem that I'm facing is pulling the AngularJS app into the host React app (app 1).
I'm getting a lot of errors in the console but I think this is the most relevant one:
xxx.js:701 Uncaught Error: application 'AngularJS' died in status LOADING_SOURCE_CODE: Module parse failed: Unexpected token (1:1)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> <!DOCTYPE html>
| <html lang="en" ng-app="MyAngularJSApp" ng-controller="MyAngularJSCtrl">
| <head>
at eval (index.html:1:7)
at ./index.html (node_modules_angularjs-bootstrap-datetimepicker_node_modules_moment_locale_sync_recursive_-no-1f57b2.app.bundle.js:27:1)
at __webpack_require__ (remoteEntry.js:44:42)
at eval (app.entry.js:21:69)
at ./app/app.entry.js (node_modules_angularjs-bootstrap-datetimepicker_node_modules_moment_locale_sync_recursive_-no-1f57b2.app.bundle.js:113:1)
at __webpack_require__ (remoteEntry.js:44:42)
at eval (container_entry:3:298)
at u.m.<computed> (bundle.js:2:262091)
at u (bundle.js:2:259451)
This is my current set up:
AngularJS App:
We're using webpack so we have a webpack.config.js file and the most important part is:
module.exports = function (env = {}) {
const isProd = !!env.prod;
const buildpath = env.buildpath ? env.buildpath : isProd ? 'dist' : 'build';
return {
entry: path.resolve(__dirname, 'app/app.entry.js'),
output: {
path: path.resolve(__dirname, buildpath),
filename: 'js/app.bundle.js',
clean: true,
},
plugins: [
new webpack.container.ModuleFederationPlugin({
name: 'MyAngularJSApp',
filename: 'remoteEntry.js',
remotes: {},
exposes: {
'./App': path.resolve(__dirname, 'app/app.entry.js'),
},
}),
new HtmlWebpackPlugin({
inject: 'body',
template: path.resolve(__dirname, 'index.html'),
filename: path.resolve(buildpath, 'index.html'),
})
],
module: {
......
},
resolve: {
......
},
optimization: {
......
},
};
};
In app/app.entry.js I've added the following:
import singleSpaAngularJS from 'single-spa-angularjs';
import angular from 'angular';
import app from '../index.html'
const domElementGetter = () => document.getElementById('AngularJSApp');
const ngLifecycles = singleSpaAngularJS({
angular: angular,
domElementGetter,
mainAngularModule: 'AngularJSApp',
template: app,
});
export const bootstrap = ngLifecycles.bootstrap;
export const mount = ngLifecycles.mount;
export const unmount = ngLifecycles.unmount;
In the host/consuming React application:
webpack.config.js:
const path = require('path');
const webpack = require('webpack')
const { ModuleFederationPlugin } = webpack.container;
const HtmlWebpackPlugin = require("html-webpack-plugin");
const dependencies = require('./package.json').dependencies;
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
entry: './src/index.tsx',
mode: 'production',
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
module: {
rules: [
{
test: /\.(jsx|js)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.html$/,
exclude: /\.lazy\.html$/,
use: [
{
loader: 'html-loader',
options: {
minimize: true,
},
},
],
},
// Use Babel to transpile TypeScript and TypeScript / React files to ES5
{
test: /\.(tsx|ts)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new ModuleFederationPlugin({
name: 'container',
remotes: {
reactMFE: 'reactMFE#http://localhost:3000/remoteEntry.js',
AngularJSApp: 'AngularJSApp#http://localhost:3004/remoteEntry.js',
},
shared: {
react: {
singleton: true,
eager: true,
requiredVersion: dependencies.react,
},
'react-dom': {
singleton: true,
eager: true,
requiredVersion: dependencies['react-dom'],
}
},
}),
new webpack.DefinePlugin({ // <-- key to reducing React's size
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
}),
// new BundleAnalyzerPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
I've registered the Angular app in the entry point /src/index.tsx. The same file also imports 'bootstrap' to resolve the eager consumption error when using module federation:
/src/index.tsx:
import("./bootstrap")
import {registerApplication} from 'single-spa'
registerApplication(
'AngularJSApp',
//#ts-ignore
() => import('AngularJSApp/App'),
location => location.pathname.startsWith('/')
)
/src/bootstrap.tsx:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
I've made changes to load in the scripts for the imported apps in public/index.html:
<html>
<head>
<script src="http://localhost:3000/remoteEntry.js"></script>
<script src="http://localhost:3004/remoteEntry.js"></script>
</head>
<body>
<div>Container App</div>
<div id="root"></div>
{/* I've put these two lines as a last ditch attempt - not sure which one of them should be working*/}
<div id="AngularJSApp"></div>
<div id="single-spa-application:AngularJSApp"></div>
</body>
</html>
Can someone advise on how I can get this working, it would be much appreciated.
Thank you

Module Federation, React, and Apollo 3 warnings

I'm building an app with micro-frontends using webpack 5's module federation plugin. Everything was fine until I started adding react hooks into my remote app. At that point I received errors about "invalid usage of hooks", i.e. I discovered I had TWO versions of react loaded, one from the remote and one from the app consuming the remote.
That problem was solved by adding a shared key to the ModuleFederationPlugin section of my webpack config that marked React as a singleton. Now everything compiles and seems to run just fine.
However, the webpack compiler is throwing some annoying warnings at me now. Its saying:
No required version specified and unable to automatically determine one. Unable to find required version for "react" in description file (/Users/myComputer/Development/myapp/node_modules/#apollo/client/react/context/package.json). It need to be in dependencies, devDependencies or peerDependencies.
Here is what my webpack config (in the remote) looks like currently:
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
const deps = require('./package.json').dependencies
module.exports = {
mode: 'development',
devServer: { port: 3001 },
entry: './src/index.tsx',
output: {
path: __dirname + '/dist/',
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'],
},
use: 'ts-loader',
},
]
},
devtool: 'source-map',
plugins: [
new ModuleFederationPlugin(
{
name: 'myRemote',
filename: 'remoteEntry.js',
exposes: {
'./App':
'./src/App/App.tsx',
},
shared: {
'react': {
singleton: true,
version: deps['react'],
},
'react-dom': {
singleton: true,
version: deps['react-dom'],
},
},
}
),
new HtmlWebpackPlugin({
template:
'./index.html',
})
]
}
The consuming app's webpack config is almost the same, especially the shared section (there are some slight differences in that it declares the remotes).
What would be the way to tell webpack that the apollo package will be getting its react dependency from somewhere else? Or if thats not the right thing to tell webpack, what is and how can I get rid of these warnings?
Fixed my own problem by changing the key version to requiredVersion

Resources