Skip to content

feat: add working code for dynamic remotes and routes #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"./react-18-server-2-server",
"./react-18-server-2-server/*",
"./react-18-ssr",
"./react-dynamic-routes/*",
"./react-hmr",
"./react-hmr/*",
"./react-in-vue",
Expand Down Expand Up @@ -187,5 +188,8 @@
"cypress:run": "cypress run --config-file cypress/config/cypress.config.ts --browser=chrome",
"report:generate": "allure generate ./cypress/results/allure-results --clean -o ./cypress/report",
"report:open": "allure open ./cypress/report"
},
"dependencies": {
"puppeteer": "^20.2.0"
}
}
26 changes: 26 additions & 0 deletions react-dynamic-routes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Dynamic System Host Example

This example demos a basic host application loading remote component.

- `app1` is the host application.
- `app2` standalone application which exposes `Widget` component.
- `app3` standalone application which exposes `Widget` component that requires
`momentjs`.

# Running Demo

Run `yarn start`. This will build and serve both `app1`, `app2`, and `app3` on
ports `3001`, `3002`, and `3003` respectively.

- [localhost:3001](http://localhost:3001/) (HOST)
- [localhost:3002](http://localhost:3002/) (STANDALONE REMOTE)
- [localhost:3003](http://localhost:3003/) (STANDALONE REMOTE)
<img src="https://ssl.google-analytics.com/collect?v=1&t=event&ec=email&ea=open&t=event&tid=UA-120967034-1&z=1589682154&cid=ae045149-9d17-0367-bbb0-11c41d92b411&dt=ModuleFederationExamples&dp=/email/DynamicSystemHost">

# Running Cypress E2E Tests

To run tests in interactive mode, run `npm run cypress:debug` from the root directory of the project. It will open Cypress Test Runner and allow to run tests in interactive mode. [More info about "How to run tests"](../../cypress/README.md#how-to-run-tests)

To build app and run test in headless mode, run `yarn e2e:ci`. It will build app and run tests for this workspace in headless mode. If tets failed cypress will create `cypress` directory in sample root folder with screenshots and videos.

["Best Practices, Rules amd more interesting information here](../../cypress/README.md)
27 changes: 27 additions & 0 deletions react-dynamic-routes/app1/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "react-dynamic-routes_app1",
"version": "0.0.0",
"private": true,
"devDependencies": {
"@babel/core": "7.18.9",
"@babel/preset-react": "7.18.6",
"babel-loader": "8.2.5",
"html-webpack-plugin": "5.5.1",
"serve": "13.0.4",
"webpack": "5.72.1",
"webpack-cli": "4.10.0",
"webpack-dev-server": "4.13.3"
},
"scripts": {
"start": "webpack-cli serve",
"build": "webpack --mode production",
"serve": "serve dist -p 3001",
"clean": "rm -rf dist"
},
"dependencies": {
"@module-federation/utilities": "^1.4.0",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-router-dom": "^6.10.0"
}
}
Binary file added react-dynamic-routes/app1/public/favicon.ico
Binary file not shown.
6 changes: 6 additions & 0 deletions react-dynamic-routes/app1/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<html>
<head></head>
<body>
<div id="root"></div>
</body>
</html>
51 changes: 51 additions & 0 deletions react-dynamic-routes/app1/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import { importRemote } from '@module-federation/utilities';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import remoteListJson from './remotes.json';

const MainPage = () => {
return (<div
style={{
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
}}
>
<h1>React Dynamic Routes</h1>
<h2>App 1</h2>
<p>
The Dynamic Routes will take advantage Module Federation <strong>remotes</strong> and{' '}
<strong>exposes</strong>. It will not load any components or modules that have been loaded
already. It will create the routes at runtime from a json list. The goal is to support a list
of remotes in a registry, such that we don't need to redeploy the host when we add new remotes.
</p>
<Link to='app2'>Go to App 2</Link>
<br />
<Link to='app3'>Go to App 3</Link>
<br />
</div>)
}

const AppComponent = (elementJson, index) => {
const RemoteComponent = React.lazy(() => importRemote(
{ url: elementJson.url,
scope: elementJson.scope,
module: elementJson.module
})
);
const path = `${elementJson.scope}/*`;
return (<Route key={index} path={path} element={ <RemoteComponent /> } />);
}

export default function App() {
return (
<BrowserRouter>
<React.Suspense fallback="Loading System">
<Routes>
<Route path='/' element= { <MainPage />}/>
{remoteListJson.remotes.map((elementJson, index) => AppComponent(elementJson, index))}
<Route path='*' element= { <h1>Error</h1> }/>
</Routes>
</React.Suspense>
</BrowserRouter>
);
}
5 changes: 5 additions & 0 deletions react-dynamic-routes/app1/src/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import App from './App';
import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(<App />, document.getElementById('root'));
1 change: 1 addition & 0 deletions react-dynamic-routes/app1/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import('./bootstrap');
14 changes: 14 additions & 0 deletions react-dynamic-routes/app1/src/remotes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"remotes": [
{
"url": "http://localhost:3002",
"scope": "app2",
"module": "./Widget"
},
{
"url": "http://localhost:3003",
"scope": "app3",
"module": "./Widget"
}
]
}
69 changes: 69 additions & 0 deletions react-dynamic-routes/app1/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin;
const path = require('path');

module.exports = {
mode: 'development',
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
publicPath: '/'
},
port: 3001,
historyApiFallback: true,
},
output: {
publicPath: 'auto',
},
resolve: {
fallback: {
"fs": false,
"tls": false,
"net": false,
"path": false,
"zlib": false,
"http": false,
"https": false,
"stream": false,
"crypto": false,
"crypto-browserify": require.resolve('crypto-browserify'), //if you want to use this module also don't forget npm i crypto-browserify
}
},
module: {
rules: [
{
test: /\.m?js$/,
type: 'javascript/auto',
resolve: {
fullySpecified: false,
},
},
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react'],
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'app1',
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
moment: { singleton: true },
'react-router-dom': { singleton: true },
},
}),
new HtmlWebpackPlugin({
template: './public/index.html',
favicon: './public/favicon.ico',
publicPath: '/',
}),
],
};
// This fixes a bug with webpack where subroutes in the child pull main from wrong location
// https://github.com/module-federation/module-federation-examples/pull/2170
Loading