Skip to content

Commit 25907c8

Browse files
Module Federation
1 parent 3374957 commit 25907c8

26 files changed

+2772
-437
lines changed

.travis.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ deploy:
2121
on:
2222
all_branches: true
2323
condition: $TRAVIS_TAG != "" && $TRAVIS_TAG != *"-"*
24-
repo: ringcentral/ringcentral-web-phone
24+
repo: ringcentral/web-apps
2525
- provider: script
2626
script: yarn publish:release ${TRAVIS_TAG} --yes --dist-tag next
2727
skip_cleanup: true
2828
on:
2929
all_branches: true
3030
condition: $TRAVIS_TAG == *"-"*
31-
repo: ringcentral/ringcentral-web-phone
31+
repo: ringcentral/web-apps
3232

3333
after_success: yarn test:coverage

README.md

+234-78
Large diffs are not rendered by default.

demo/fed/package.json

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "@web-apps/fed",
3+
"version": "0.0.0",
4+
"private": true,
5+
"devDependencies": {
6+
"@babel/core": "7.10.2",
7+
"@babel/preset-react": "7.10.1",
8+
"babel-loader": "8.1.0",
9+
"serve": "11.3.0",
10+
"webpack": "^5.0.0-beta.17",
11+
"webpack-cli": "3.3.11",
12+
"webpack-dev-server": "3.11.0"
13+
},
14+
"scripts": {
15+
"start": "webpack-dev-server"
16+
},
17+
"dependencies": {
18+
"react": "^16.13.0",
19+
"react-dom": "^16.13.0",
20+
"moment": "^2.24.0"
21+
}
22+
}

demo/fed/src/App.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react';
2+
import moment from 'moment';
3+
4+
export default ({foo}) => (
5+
<div
6+
style={{
7+
borderRadius: '4px',
8+
padding: '2em',
9+
backgroundColor: 'red',
10+
color: 'white',
11+
}}
12+
>
13+
<h2>App 2 Widget: {foo}</h2>
14+
<p>{moment().format('MMMM Do YYYY, h:mm:ss a')}</p>
15+
</div>
16+
);

demo/fed/src/index.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import App from './App';
2+
import React from 'react';
3+
import ReactDOM from 'react-dom';
4+
5+
export default node => {
6+
ReactDOM.render(<App foo={node.getAttribute('foo')} />, node);
7+
return () => ReactDOM.unmountComponentAtNode(node);
8+
};

demo/fed/webpack.config.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
const {ModuleFederationPlugin} = require('webpack').container;
2+
const path = require('path');
3+
4+
module.exports = {
5+
entry: './src/index',
6+
mode: 'development',
7+
devServer: {
8+
contentBase: path.join(__dirname, 'dist'),
9+
port: process.env.REACT_APP_FED_PORT,
10+
},
11+
output: {
12+
publicPath: `http://localhost:${process.env.REACT_APP_FED_PORT}/`,
13+
},
14+
module: {
15+
rules: [
16+
{
17+
test: /\.jsx?$/,
18+
loader: 'babel-loader',
19+
exclude: /node_modules/,
20+
options: {
21+
presets: ['@babel/preset-react'],
22+
},
23+
},
24+
],
25+
},
26+
plugins: [
27+
new ModuleFederationPlugin({
28+
name: 'web_app_federation',
29+
library: {type: 'var', name: 'web_app_federation'},
30+
filename: 'remoteEntry.js',
31+
exposes: {
32+
'./App': './src/App',
33+
'./index': './src/index',
34+
},
35+
shared: {
36+
'react-dom': 'react-dom',
37+
moment: '^2.24.0',
38+
react: {
39+
import: 'react', // the "react" package will be used a provided and fallback module
40+
shareKey: 'react', // under this name the shared module will be placed in the share scope
41+
shareScope: 'default', // share scope with this name will be used
42+
singleton: true, // only a single version of the shared module is allowed
43+
},
44+
},
45+
}),
46+
],
47+
};

demo/host/package.json

+10-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.0.0",
44
"private": true,
55
"scripts": {
6-
"start": "PORT=$REACT_APP_HOST_PORT react-scripts start"
6+
"start": "webpack-dev-server"
77
},
88
"browserslist": [
99
"IE 11"
@@ -20,6 +20,14 @@
2020
"react-router-dom": "5.1.2"
2121
},
2222
"devDependencies": {
23-
"react-scripts": "3.0.1"
23+
"@babel/core": "7.10.2",
24+
"@babel/preset-react": "7.10.1",
25+
"babel-loader": "8.1.0",
26+
"css-loader": "^3.5.3",
27+
"style-loader": "^1.2.1",
28+
"html-webpack-plugin": "^4.3.0",
29+
"webpack": "^5.0.0-beta.17",
30+
"webpack-cli": "3.3.11",
31+
"webpack-dev-server": "3.11.0"
2432
}
2533
}

demo/host/src/bootstrap.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter';
2+
import '@webcomponents/webcomponentsjs';
3+
import React from 'react';
4+
import {render} from 'react-dom';
5+
import Router from './Router';
6+
7+
import 'bootstrap/dist/css/bootstrap.css';
8+
import '@ringcentral/web-apps-host-css/styles.css';
9+
import './index.css';
10+
11+
const rootEl = document.getElementById('app');
12+
13+
render(<Router />, rootEl);
14+
15+
if (module.hot) module.hot.accept();

demo/host/src/index.js

+1-15
Original file line numberDiff line numberDiff line change
@@ -1,15 +1 @@
1-
import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter';
2-
import '@webcomponents/webcomponentsjs';
3-
import React from 'react';
4-
import {render} from 'react-dom';
5-
import Router from './Router';
6-
7-
import 'bootstrap/dist/css/bootstrap.css';
8-
import '@ringcentral/web-apps-host-css/styles.css';
9-
import './index.css';
10-
11-
const rootEl = document.getElementById('app');
12-
13-
render(<Router />, rootEl);
14-
15-
if (module.hot) module.hot.accept();
1+
import('./bootstrap');

demo/host/src/lib/registry.js

+16
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,20 @@ export const appRegistry = {
5252
url + `/main.js`,
5353
],
5454
},
55+
federation: {
56+
type: 'global',
57+
getUrl: async (url = `http://localhost:${process.env.REACT_APP_FED_PORT}`) => url + '/remoteEntry.js',
58+
options: {
59+
federation: true,
60+
},
61+
},
62+
federationDirect: {
63+
type: 'global',
64+
getUrl: async (url = `http://localhost:${process.env.REACT_APP_FED_PORT}`) => url + '/remoteEntry.js',
65+
options: {
66+
federation: true,
67+
module: './App',
68+
scope: 'web_app_federation',
69+
},
70+
},
5571
};

demo/host/src/lib/useAppRegistry.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ export const useAppRegistry = appId => {
2828
if (!appRegistry[appId]) throw new Error(`App ${appId} not found in registry`);
2929

3030
// registry itself can also be loaded from API for example
31-
const {getUrl, type, origin} = appRegistry[appId];
31+
const {getUrl, ...rest} = appRegistry[appId];
3232

3333
// this allows to override url of app, useful for production to swap deployed version with local
3434
const {appsOverrides} = window.localStorage;
3535

3636
const url = await getUrl(appsOverrides && appsOverrides[appId] && appsOverrides[appId].url);
3737

38-
if (mounted) dispatch({type: 'success', payload: {id: appId, url, type, origin}}); // by setting ID to state we make sure that id, url and type are in sync
38+
if (mounted) dispatch({type: 'success', payload: {id: appId, url, ...rest}}); // by setting ID to state we make sure that id, url and type are in sync
3939
} catch (error) {
4040
if (mounted) dispatch({type: 'error', payload: error});
4141
}

demo/host/src/pages/App.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ const App = ({
3030
},
3131
}) => {
3232
const [authtoken, setAuthToken] = useState('set-by-host');
33-
const {id, url, type, error: registryError, loading: registryLoading, origin} = useAppRegistry(appId);
33+
const {id, url, type, error: registryError, loading: registryLoading, origin, options} = useAppRegistry(appId);
3434

35-
const {error: appError, Component, node, loading: appLoading} = useApplication({id, type, url});
35+
const {error: appError, Component, node, loading: appLoading} = useApplication({id, type, url, options});
3636

3737
const [popup, setPopup] = useState(false);
3838
const onPopup = useCallback(event => setPopup(popup => (popup !== event.detail ? event.detail : popup)), []);

demo/host/src/pages/FederationApp.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React, {memo, useEffect, useState} from 'react';
2+
import {appRegistry} from '../lib/registry';
3+
import {useApplication, useListenerEffect, eventType} from '@ringcentral/web-apps-host-react';
4+
5+
const id = 'federation';
6+
7+
export const FederationApp = memo(({logout}) => {
8+
const [url, setUrl] = useState(null);
9+
10+
useEffect(() => {
11+
let mounted = true;
12+
appRegistry[id].getUrl().then(url => mounted && setUrl(url));
13+
return () => (mounted = false);
14+
}, []);
15+
16+
const {error, loading, Component, node} = useApplication({
17+
id,
18+
type: appRegistry[id].type,
19+
url,
20+
options: appRegistry[id].options,
21+
});
22+
23+
useListenerEffect(node, eventType.message, e => e.detail.logout && logout());
24+
25+
let render;
26+
if (!url) render = <>Loading federation app...</>;
27+
else if (error) render = <>Error in federation: {error.toString()}</>;
28+
else if (loading) render = <>Loading federation app...</>;
29+
30+
return (
31+
<div className="navbar-container">
32+
{render && (
33+
<nav className="navbar navbar-expand-lg navbar-light bg-light" style={{justifyContent: 'center'}}>
34+
<span className="navbar-text">{render}</span>
35+
</nav>
36+
)}
37+
<Component foo="bar" />
38+
</div>
39+
);
40+
});
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React, {memo, useEffect, useState} from 'react';
2+
import {appRegistry} from '../lib/registry';
3+
import {useApplication, useListenerEffect, eventType} from '@ringcentral/web-apps-host-react';
4+
5+
const id = 'federationDirect';
6+
7+
export const FederationDirectApp = memo(({logout}) => {
8+
const [url, setUrl] = useState(null);
9+
10+
useEffect(() => {
11+
let mounted = true;
12+
appRegistry[id].getUrl().then(url => mounted && setUrl(url));
13+
return () => (mounted = false);
14+
}, []);
15+
16+
const {error, loading, Component, node} = useApplication({
17+
id,
18+
type: appRegistry[id].type,
19+
url,
20+
options: appRegistry[id].options,
21+
});
22+
23+
useListenerEffect(node, eventType.message, e => e.detail.logout && logout());
24+
25+
let render;
26+
if (!url) render = <>Loading federation app...</>;
27+
else if (error) render = <>Error in federation: {error.toString()}</>;
28+
else if (loading) render = <>Loading federation app...</>;
29+
30+
return (
31+
<div className="navbar-container">
32+
{render && (
33+
<nav className="navbar navbar-expand-lg navbar-light bg-light" style={{justifyContent: 'center'}}>
34+
<span className="navbar-text">{render}</span>
35+
</nav>
36+
)}
37+
<Component direct foo="bar" />
38+
</div>
39+
);
40+
});

demo/host/src/pages/LoggedInWrapper.js

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import App from './App';
44
import Index from './Index';
55
import {MenuApp} from './MenuApp';
66
import {MenuAppIframe} from './MenuAppIframe';
7+
import {FederationApp} from './FederationApp';
8+
import {FederationDirectApp} from './FederationDirectApp';
79

810
const logout = () => alert('Logout');
911

@@ -16,6 +18,9 @@ const Layout = () => (
1618
<Route path="/application/apps" component={Index} exact />
1719
<Route path="/application/apps/:appId" component={App} />
1820
</Switch>
21+
22+
<FederationApp />
23+
<FederationDirectApp />
1924
</>
2025
);
2126

demo/host/src/pages/MenuApp.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const MenuApp = memo(({logout}) => {
2828

2929
let render;
3030
if (!url) render = <>Loading menu config...</>;
31-
else if (error) render = <>Error in menu: {error}</>;
31+
else if (error) render = <>Error in menu: {error.toString()}</>;
3232
else if (loading) render = <>Loading menu app...</>;
3333

3434
return (

demo/host/src/pages/MenuAppIframe.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const MenuAppIframe = memo(({logout}) => {
2323

2424
let render;
2525
if (!url) render = <>Loading menu config...</>;
26-
else if (error) render = <>Error in menu: {error}</>;
26+
else if (error) render = <>Error in menu: {error.toString()}</>;
2727
else if (loading) render = <>Loading menu app...</>;
2828

2929
return (

0 commit comments

Comments
 (0)