Skip to content

Commit

Permalink
Module Federation
Browse files Browse the repository at this point in the history
  • Loading branch information
kirill-konshin committed Jun 9, 2020
1 parent 3374957 commit 25907c8
Show file tree
Hide file tree
Showing 26 changed files with 2,772 additions and 437 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ deploy:
on:
all_branches: true
condition: $TRAVIS_TAG != "" && $TRAVIS_TAG != *"-"*
repo: ringcentral/ringcentral-web-phone
repo: ringcentral/web-apps
- provider: script
script: yarn publish:release ${TRAVIS_TAG} --yes --dist-tag next
skip_cleanup: true
on:
all_branches: true
condition: $TRAVIS_TAG == *"-"*
repo: ringcentral/ringcentral-web-phone
repo: ringcentral/web-apps

after_success: yarn test:coverage
312 changes: 234 additions & 78 deletions README.md

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions demo/fed/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@web-apps/fed",
"version": "0.0.0",
"private": true,
"devDependencies": {
"@babel/core": "7.10.2",
"@babel/preset-react": "7.10.1",
"babel-loader": "8.1.0",
"serve": "11.3.0",
"webpack": "^5.0.0-beta.17",
"webpack-cli": "3.3.11",
"webpack-dev-server": "3.11.0"
},
"scripts": {
"start": "webpack-dev-server"
},
"dependencies": {
"react": "^16.13.0",
"react-dom": "^16.13.0",
"moment": "^2.24.0"
}
}
16 changes: 16 additions & 0 deletions demo/fed/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import moment from 'moment';

export default ({foo}) => (
<div
style={{
borderRadius: '4px',
padding: '2em',
backgroundColor: 'red',
color: 'white',
}}
>
<h2>App 2 Widget: {foo}</h2>
<p>{moment().format('MMMM Do YYYY, h:mm:ss a')}</p>
</div>
);
8 changes: 8 additions & 0 deletions demo/fed/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import App from './App';
import React from 'react';
import ReactDOM from 'react-dom';

export default node => {
ReactDOM.render(<App foo={node.getAttribute('foo')} />, node);
return () => ReactDOM.unmountComponentAtNode(node);
};
47 changes: 47 additions & 0 deletions demo/fed/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const {ModuleFederationPlugin} = require('webpack').container;
const path = require('path');

module.exports = {
entry: './src/index',
mode: 'development',
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: process.env.REACT_APP_FED_PORT,
},
output: {
publicPath: `http://localhost:${process.env.REACT_APP_FED_PORT}/`,
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react'],
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'web_app_federation',
library: {type: 'var', name: 'web_app_federation'},
filename: 'remoteEntry.js',
exposes: {
'./App': './src/App',
'./index': './src/index',
},
shared: {
'react-dom': 'react-dom',
moment: '^2.24.0',
react: {
import: 'react', // the "react" package will be used a provided and fallback module
shareKey: 'react', // under this name the shared module will be placed in the share scope
shareScope: 'default', // share scope with this name will be used
singleton: true, // only a single version of the shared module is allowed
},
},
}),
],
};
12 changes: 10 additions & 2 deletions demo/host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.0",
"private": true,
"scripts": {
"start": "PORT=$REACT_APP_HOST_PORT react-scripts start"
"start": "webpack-dev-server"
},
"browserslist": [
"IE 11"
Expand All @@ -20,6 +20,14 @@
"react-router-dom": "5.1.2"
},
"devDependencies": {
"react-scripts": "3.0.1"
"@babel/core": "7.10.2",
"@babel/preset-react": "7.10.1",
"babel-loader": "8.1.0",
"css-loader": "^3.5.3",
"style-loader": "^1.2.1",
"html-webpack-plugin": "^4.3.0",
"webpack": "^5.0.0-beta.17",
"webpack-cli": "3.3.11",
"webpack-dev-server": "3.11.0"
}
}
15 changes: 15 additions & 0 deletions demo/host/src/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter';
import '@webcomponents/webcomponentsjs';
import React from 'react';
import {render} from 'react-dom';
import Router from './Router';

import 'bootstrap/dist/css/bootstrap.css';
import '@ringcentral/web-apps-host-css/styles.css';
import './index.css';

const rootEl = document.getElementById('app');

render(<Router />, rootEl);

if (module.hot) module.hot.accept();
16 changes: 1 addition & 15 deletions demo/host/src/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1 @@
import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter';
import '@webcomponents/webcomponentsjs';
import React from 'react';
import {render} from 'react-dom';
import Router from './Router';

import 'bootstrap/dist/css/bootstrap.css';
import '@ringcentral/web-apps-host-css/styles.css';
import './index.css';

const rootEl = document.getElementById('app');

render(<Router />, rootEl);

if (module.hot) module.hot.accept();
import('./bootstrap');
16 changes: 16 additions & 0 deletions demo/host/src/lib/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,20 @@ export const appRegistry = {
url + `/main.js`,
],
},
federation: {
type: 'global',
getUrl: async (url = `http://localhost:${process.env.REACT_APP_FED_PORT}`) => url + '/remoteEntry.js',
options: {
federation: true,
},
},
federationDirect: {
type: 'global',
getUrl: async (url = `http://localhost:${process.env.REACT_APP_FED_PORT}`) => url + '/remoteEntry.js',
options: {
federation: true,
module: './App',
scope: 'web_app_federation',
},
},
};
4 changes: 2 additions & 2 deletions demo/host/src/lib/useAppRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ export const useAppRegistry = appId => {
if (!appRegistry[appId]) throw new Error(`App ${appId} not found in registry`);

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

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

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

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
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
} catch (error) {
if (mounted) dispatch({type: 'error', payload: error});
}
Expand Down
4 changes: 2 additions & 2 deletions demo/host/src/pages/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ const App = ({
},
}) => {
const [authtoken, setAuthToken] = useState('set-by-host');
const {id, url, type, error: registryError, loading: registryLoading, origin} = useAppRegistry(appId);
const {id, url, type, error: registryError, loading: registryLoading, origin, options} = useAppRegistry(appId);

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

const [popup, setPopup] = useState(false);
const onPopup = useCallback(event => setPopup(popup => (popup !== event.detail ? event.detail : popup)), []);
Expand Down
40 changes: 40 additions & 0 deletions demo/host/src/pages/FederationApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, {memo, useEffect, useState} from 'react';
import {appRegistry} from '../lib/registry';
import {useApplication, useListenerEffect, eventType} from '@ringcentral/web-apps-host-react';

const id = 'federation';

export const FederationApp = memo(({logout}) => {
const [url, setUrl] = useState(null);

useEffect(() => {
let mounted = true;
appRegistry[id].getUrl().then(url => mounted && setUrl(url));
return () => (mounted = false);
}, []);

const {error, loading, Component, node} = useApplication({
id,
type: appRegistry[id].type,
url,
options: appRegistry[id].options,
});

useListenerEffect(node, eventType.message, e => e.detail.logout && logout());

let render;
if (!url) render = <>Loading federation app...</>;
else if (error) render = <>Error in federation: {error.toString()}</>;
else if (loading) render = <>Loading federation app...</>;

return (
<div className="navbar-container">
{render && (
<nav className="navbar navbar-expand-lg navbar-light bg-light" style={{justifyContent: 'center'}}>
<span className="navbar-text">{render}</span>
</nav>
)}
<Component foo="bar" />
</div>
);
});
40 changes: 40 additions & 0 deletions demo/host/src/pages/FederationDirectApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, {memo, useEffect, useState} from 'react';
import {appRegistry} from '../lib/registry';
import {useApplication, useListenerEffect, eventType} from '@ringcentral/web-apps-host-react';

const id = 'federationDirect';

export const FederationDirectApp = memo(({logout}) => {
const [url, setUrl] = useState(null);

useEffect(() => {
let mounted = true;
appRegistry[id].getUrl().then(url => mounted && setUrl(url));
return () => (mounted = false);
}, []);

const {error, loading, Component, node} = useApplication({
id,
type: appRegistry[id].type,
url,
options: appRegistry[id].options,
});

useListenerEffect(node, eventType.message, e => e.detail.logout && logout());

let render;
if (!url) render = <>Loading federation app...</>;
else if (error) render = <>Error in federation: {error.toString()}</>;
else if (loading) render = <>Loading federation app...</>;

return (
<div className="navbar-container">
{render && (
<nav className="navbar navbar-expand-lg navbar-light bg-light" style={{justifyContent: 'center'}}>
<span className="navbar-text">{render}</span>
</nav>
)}
<Component direct foo="bar" />
</div>
);
});
5 changes: 5 additions & 0 deletions demo/host/src/pages/LoggedInWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import App from './App';
import Index from './Index';
import {MenuApp} from './MenuApp';
import {MenuAppIframe} from './MenuAppIframe';
import {FederationApp} from './FederationApp';
import {FederationDirectApp} from './FederationDirectApp';

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

Expand All @@ -16,6 +18,9 @@ const Layout = () => (
<Route path="/application/apps" component={Index} exact />
<Route path="/application/apps/:appId" component={App} />
</Switch>

<FederationApp />
<FederationDirectApp />
</>
);

Expand Down
2 changes: 1 addition & 1 deletion demo/host/src/pages/MenuApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const MenuApp = memo(({logout}) => {

let render;
if (!url) render = <>Loading menu config...</>;
else if (error) render = <>Error in menu: {error}</>;
else if (error) render = <>Error in menu: {error.toString()}</>;
else if (loading) render = <>Loading menu app...</>;

return (
Expand Down
2 changes: 1 addition & 1 deletion demo/host/src/pages/MenuAppIframe.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const MenuAppIframe = memo(({logout}) => {

let render;
if (!url) render = <>Loading menu config...</>;
else if (error) render = <>Error in menu: {error}</>;
else if (error) render = <>Error in menu: {error.toString()}</>;
else if (loading) render = <>Loading menu app...</>;

return (
Expand Down
Loading

0 comments on commit 25907c8

Please sign in to comment.