Angular result example:
application-name
= angular-app | vue-app | react-app
Repeat in 3 terminals:
yarn
:
cd `application-name`
yarn
yarn serve
npm
:
cd `application-name`
npm i
npm run serve
apps will be served on:
- Angular - http://localhost:4201/
- Vue - http://localhost:8081/
- React - http://localhost:3001/
-
React works very bad with imported Web Components, you can't listen custom events with
onEvent
template listeners. You can only use this way, but reactive prop re-render inside web component will be lost:only prop binding
<vue-header title={wcTitle} />
or eventListener with no reactivity
const myEl = document.createElement('my-element'); myEl.prop = 'hello'; // or myEl.setAttribute('prop', 'hello'); myEl.addEventListener('customEvent' func);
-
Angular and Vue works nice with Web Components, there are easy ways to pass props and listen to events:
Angular:
<vue-header [title]="wcTitle" (headerclick)="log($event)"></vue-header>
Vue:
<ng-header :title="wcTitle" @headerclick="log($event)"></ng-header>
-
Angular provides awesome api with
detectChanges()
function, so you can mutate your component on fly
- To create Angular Web Component, you must import
zone
inside utils file. Also this behavior may be evaded. Check inside utils file. - To render React Element, you must import
React
inside utils file. - In Vue component, to use both
Web Components
andnative render
you must copy-paste your styles from <style> tag into styles[]
✅ - Ok
Angular
type | Render | Input | Output |
---|---|---|---|
Native | ✅ | ✅ | ✅ |
Web Components | ✅ | ✅ | ✅ |
with
detectChanges()
you can update your inputs on hot
Vue:
type | Render | Input | Output |
---|---|---|---|
Native | ✅ | ✅ | |
Web Components | ✅ | ✅ | ✅ |
React
type | Render | Input | Output |
---|---|---|---|
Native | ✅ | ✅ | |
Web Components | ❔ | ❔ | ❔ |
Web Components from React seems to work ok, but it's tricky way to implement this
-
Init apps
-
Add some shared components
-
Setup webpack
Plugin config example:
new ModuleFederationPlugin({ name: 'vueApp', filename: 'remoteEntry.js', // elements that we importing remotes: { angularApp: 'angularApp@http://localhost:4201/remoteEntry.js', reactApp: 'reactApp@http://localhost:3001/remoteEntry.js', }, // elements that we exporting exposes: { './Header': './src/components/Header', './utils': './src/utils', }, shared: require('./package.json').dependencies, }),
3.1 Angular:
ng add @angular-architects/module-federation
Select project if monorepo. All files will be created 😎, just edit webpack.config.js to configure ModuleFederationPlugin
3.2 Vue:
- Install necessary packages
yarn add webpack @vue/[email protected] @vue/[email protected] -D
or
npm i webpack @vue/[email protected] @vue/[email protected] -D
If you use PWA, also install
@vue/[email protected]
-
Create vue.config.js that exports webpack configuration with ModuleFederationPlugin setup
-
Create asynchronous boundary
3.3 React:
- Update necessary packages
npm i -D webpack webpack-cli webpack-server html-webpack-plugin webpack-dev-server npm i -D bundle-loader babel-loader @babel/preset-react @babel/preset-typescript
or
yarn add -D webpack webpack-cli webpack-server html-webpack-plugin webpack-dev-server yarn add -D bundle-loader babel-loader @babel/preset-react @babel/preset-typescript
-
Create webpack.config.js with ModuleFederationPlugin setup.
-
Create asynchronous boundary
-
Add new scripts to your
package.json
"scripts": { "serve": "webpack-cli serve", "build": "webpack --mode production", "serve-build": "serve dist -p 3001" },
-
Export components
4.1 Angular:
Using Angular render function:
-
Create exporting file like utils.ts
-
renderAngularComponent
function creates inside self empty ngModule withngDoBootstrap
function that bootstraps passed component to passedDOM selector
. This module have to be bootstrapped withplatformBrowserDynamic
fn:
export const renderAngularComponent: IRenderAngularComponent = ({ AngularComponent, selector, }) => { let componentRef: ComponentRef<typeof AngularComponent>; @NgModule({ imports: [BrowserModule] }) class EmptyModule implements DoBootstrap { ngDoBootstrap(appRef: ApplicationRef) { componentRef = appRef.bootstrap(AngularComponent, selector); } } return platformBrowserDynamic() .bootstrapModule(EmptyModule) .then((props) => { const newProps = { ...props, componentRef } as TRenderReturn; return newProps; }); };
- Function usage:
renderAngularComponent({ AngularComponent: AngularHeader, selector: '#ng-header', }).then(({ componentRef }) => { componentRef.instance.title = 'custom title'; componentRef.changeDetectorRef.detectChanges(); const sub = componentRef.instance.headerclick.subscribe(console.log); // also you can use unsubscribe function // sub.unsubscribe(); });
Using Web Components:
ng add @angular/elements
-
Create exporting file like utils.ts
-
Add your file to ts compilation
if you use some utils file to define helper functions, make sure, that inside your tsconfig.app.json file added new
.ts
files to prevent error "{file} is missing from the TypeScript compilation":tsconfig.app.json
"files": [ ... "src/utils.ts" ],
- Create empty module to define components by using
platformBrowserDynamic
fn
@NgModule({ imports: [BrowserModule] }) class EmptyModule implements DoBootstrap { ngDoBootstrap(appRef: ApplicationRef) {} }
- Export custom element define function
export const defineAngularWebComponent = ({ AngularComponent, name, }: { AngularComponent: Type<any>; name: string; }) => { platformBrowserDynamic() .bootstrapModule(EmptyModule) .then(({ injector }) => { const angularEl = createCustomElement(AngularComponent, { injector }); customElements.define(name, angularEl); }); };
- Pass
AngularComponent
andtag-name
todefineAngularWebComponent
. and use yourtag-name
inside html
4.2 Vue:
Using Web Components:
If you want to use WC, your styles must be declared inside component styles array. Example.
- Inside utils.js define function
export const defineVueWebComponent = ({ VueElement, name }) => { const customEl = defineCustomElement(VueElement); customElements.define(name, customEl); };
- Pass
VueElement
andtag-name
todefineVueWebComponent
. And use yourtag-name
inside html
Using Vue render function:
If you want to use Native mounting, your styles must be declared inside <style> tag. Example.
- Inside utils.js define function
export const renderVueElement = ({ VueElement, selector, props }) => { const vueApp = createApp(VueElement, props); return { vueApp, vueEl: vueApp.mount(selector) }; };
return only for get more flexability
- Pass
VueElement
,DOM selector
andprops
torenderVueElement
. It will be rendered insideDOM selector
also event listeners may be passed with converted name:
event-occurs
=>onEventOccurs
4.3 React:
- Inside utils.tsx define function that uses
ReactDOM.render
fn
ReactDOM.render( React.createElement(ReactElement, props), document.querySelector(selector) );
- Pass imported ReactElement, props,
DOM selector
-
-
Import components
There are some ways to import federated modules:
import { Component } from 'someApp/Component';
import('someApp/Component').then(({ Component }) => {});
const { Component } = await import('someApp/Component');
Also @angular-architects/module-federation
gives loadRemoteEntry
and loadRemoteModule
fns