- Published on
Angular, React, and Vue in 2023 - all in one app using Webpack 5 Module Federation
- Authors
- Name
- Nataly Chkhan
- @DarkLimeTeam
Angular, React, and Vue in 2023 - all in one app using Webpack 5 Module Federation
Title: Angular, React, and Vue in 2023 with module federation
Author: Nataly Chkhan
Subject: Module federation (Angular & React & Vue)
Duration: 5 minutes read
Language: English
Link to repo: https://github.com/darklimeteam/module-federation-angular-react-vue
Why do I need Module Federation?
The subject of this article is to go through the technical implementation of injecting React and Vue remote modules into an Angular shell app using Webpack 5's ModuleFederationPlugin. This plugin allows dividing an app into remotely loaded modules, also known as a Microfrontend approach. But why do we need Microfrontends? Let's consider a scenario where different apps have a PROFILE
page where users can view and edit their personal data, and a SETTINGS
page. We might consider the possibility of separating these pages from the main app and loading them asynchronously from remote sources.
Let’s think about possible scenario of module federation preference over monolit. In this particular case, using module federation with Microfrontends might be a good choice for a few reasons:
- Delegating the development of the
PROFILE
andSETTINGS
modules to a standalone team allows them to work independently and potentially at a faster pace. - If the main app uses a different framework or version of the framework, using module federation allows the
PROFILE
andSETTINGS
modules to be developed and maintained independently, even if they use a different framework. - If the app is large, using module federation can help break it down into smaller, more manageable pieces, making it easier to upgrade one module at a time without affecting the rest of the app.
- Deploying the
PROFILE
andSETTINGS
modules separately from the main app allows for more flexibility and faster deployment times, as the main app does not need to be rebuilt and redeployed every time a change is made to one of these modules.
If you want to learn more about Module Federation and related concepts, the list of useful links at the end of the article can be a great resource. Alternatively, if you want to see how an Angular, React, and Vue sample works, we can go through that as well. x Let’s start from scratch. 3 folders should be created (see below).
In our case, we consider Angular app as a shell, and PROFILE
together with SETTINGS
as the remote modules.
The next two sections are about React and Vue remote modules development.
React PROFILE remote module
Let’s start with the React PROFILE module implementation. Its source code could be found here. It contains a quite simple form where user can edit his name and email. Implementation is very simple. One thing you should pay attention on: bootstrap.js
file is mandatory, because of a bug described here.
To run this app do the next in the app folder:
yarn
yarn start
After that you may see the next form in your browser (port 3001):
Let’s make a closer look at ModuleFederationPlugin settings in the webpack.config.js
file
new ModuleFederationPlugin({
name: 'profile_user',
filename: 'remoteEntry.js',
exposes: {
'./ProfileReactComponent': './src/ProfileReactComponent',
},
shared: {
react: {
singleton: true,
requiredVersion: deps.react,
eager: true
},
'react-dom': {
singleton: true,
requiredVersion: deps['react-dom'],
eager: true
}
},
}),
Here ReactProfileComponent was exposed, react libs were shared. Note that all imported code, like listed in profile-style.css
will be included in exposes module together with the ReactProfileComponent.tsx.
Vue SETTINGS remote module
Vue source code could be found here.
To run Vue app do the next in the app folder:
yarn
yarn start
ModuleFederationPlugin settings in the webpack.config.js
file of Vue SETTINGS component looks like the next
new ModuleFederationPlugin({
name: 'settings_user',
filename: 'remoteEntry.js',
exposes: {
'./Settings': './src/components/Settings'
},
shared: {
vue: {
eager: true,
requiredVersion: deps.vue,
},
},
}),
defidefineCustomElement from Vue 3 is used to avoid React & Vue conflict in Angular shell app. defidefineCustomElement allows to build Vue component as an isolated element into the shadow-dom.
###Angular shell with the remotes Let’s use React PROFILE module and Vue SETTINGS module as the remotes into angular-shell
.
The question is when remotes should be loaded into the Angular shell app. The answer really depends on the requirements of the particular app. In our case, React and Vue components should appear on the homepage of Angular shell app, so LoadRemoteModule
methods run as a part of the app initialization process.
export function initializeApp(): () => void {
return () => {
loadRemoteModule({
remoteEntry: "http://localhost:3001/remoteEntry.js",
remoteName: "profile_user",
exposedModule: "./ProfileReactComponent",
});
loadRemoteModule({
remoteEntry: "http://localhost:3002/remoteEntry.js",
remoteName: "settings_user",
exposedModule: "./Settings",
});
};
}
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, RouterModule.forRoot(routes)],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
multi: true,
},
],
Pay attention on webpack.config.ts, here in WebpackFederationPlugin section remotes appear again. Next to them - shared React libs to allow angular-shell
be a federated module by itself.
new container.ModuleFederationPlugin({
name: "angular-shell",
filename: "remoteEntry.js",
remotes: {
profile_user: `profile_user@http://localhost:3001/remoteEntry.js`,
settings_user: `settings_user@http://localhost:3002/remoteEntry.js`,
},
shared: {
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
Webpack devServer headers with "Access-Control-Allow-Origin": "*"
allows to avoid CORS error.
angular.json
file should be modified to replace angular default webpack config by our custom one. See changes here.
Let’s finally move to React component injection into Angular one. profile-user.component.ts
has been created as container that utilized React component. ProfileReactComponent has been loaded asynchronically in ngAfterViewInit
. decl.d.ts
has been created to tell Angular that “profile_user” is valid import directory.
ngAfterViewInit() {
this.root = createRoot(this.containerRef.nativeElement);
this.root.render("Loading script...");
try {
import("profile_user/ProfileReactComponent").then((val) => {
this.root.render(
React.createElement(val.ProfileReactComponent, {
...this.user,
onClick: this.updateCurrentUser,
})
);
});
} catch (error) {
console.log("Erorr", error);
}
}
React PROFILE component interacts with Angular shell by onClick function usage. As a result of the name and email submitting in React component, Angular shell app invokes updateCurrentUser to change this data in app.component
.
Vue SETTINGS component is injected into settings.component.ts
wrapper. Approach is very similar to React PROFILE one. ngAfterInit from settings.component.ts
listed below
import("settings_user/Settings").then((val) => {
this.renderer.appendChild(
this.containerVueRef.nativeElement,
new val.default()
);
});
That's it. Let’s run Angular shell app. Note the use of yarn. This is required to override the webpack version for the angular cli. In angular-shell
folder run the next commands:
ng new --skip-install
ng config cli.packageManager yarn
yarn install
The app starts using port 4201.
What the next?
You might have noticed that a Vue component doesn't send data to the Angular shell. A Vue custom element uses the Shadow DOM to encapsulate component behavior, so the best approach in this case is to use an external store to get and put data through the microfrontend apps. This approach is the subject of the next article.
Thanks for reading this!