Skip to content
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

error boundary #4281

Open
wants to merge 3 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 error-boundary/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Error Boundary

This demo boots only app1, app2 remains offline.
Loading localhost:3001 will render a error message from the runtime plugin as a react module when the container if offline.
32 changes: 32 additions & 0 deletions error-boundary/app1/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "error-boundary_app1",
"version": "0.0.0",
"devDependencies": {
"@babel/core": "7.24.7",
"@babel/preset-react": "7.24.7",
"babel-loader": "9.1.3",
"html-webpack-plugin": "5.6.0",
"serve": "14.2.3",
"@rspack/core": "1.0.8",
"@rspack/cli": "1.0.8",
"@rspack/dev-server": "1.0.7",
"webpack": "5.95.0",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.1.0",
"@module-federation/runtime": "0.6.8",
"@module-federation/enhanced": "0.6.8"
},
"scripts": {
"start": "rspack serve -c rspack.config.js",
"build": "rspack build --mode production -c rspack.config.js",
"legacy:start": "webpack serve --config webpack.config.js",
"legacy:build": "webpack --config webpack.config.js --mode production",
"serve": "serve dist -p 3001",
"clean": "rm -rf dist"
},
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"lodash": "^4.17.21"
}
}
7 changes: 7 additions & 0 deletions error-boundary/app1/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head> </head>
<body>
<div id="root"></div>
</body>
</html>
101 changes: 101 additions & 0 deletions error-boundary/app1/rspack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const {
HtmlRspackPlugin,
container: { ModuleFederationPlugin },
} = require('@rspack/core');
const path = require('path');

// adds all your dependencies as shared modules
// version is inferred from package.json in the dependencies
// requiredVersion is used from your package.json
// dependencies will automatically use the highest available package
// in the federated app, based on version requirement in package.json
// multiple different versions might coexist in the federated app
// Note that this will not affect nested paths like "lodash/pluck"
// Note that this will disable some optimization on these packages
// with might lead the bundle size problems
const deps = require('./package.json').dependencies;

module.exports = {
entry: './src/index',
mode: 'development',
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
hot: true,
port: 3001,
liveReload: true,
},
target: 'web',
output: {
publicPath: 'auto',
},
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, 'src'),
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'ecmascript',
jsx: true,
},
transform: {
react: {
runtime: 'automatic',
},
},
},
},
},
},
{
test: /\.ts$/,
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
jsx: true,
},
transform: {
react: {
runtime: 'automatic',
},
},
},
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
remotes: {
app2: 'app2@http://localhost:3002/remoteEntry.js',
},
exposes: {
'./Button': './src/Button',
},
shared: {
...deps,
react: {
singleton: true,
},
'react-dom': {
singleton: true,
},
lodash: {},
},
}),
new HtmlRspackPlugin({
template: './public/index.html',
}),
],
};
95 changes: 95 additions & 0 deletions error-boundary/app1/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';
import ReactDOM from 'react-dom';
import lodash from 'lodash';

import LocalButton from './Button';
const RemoteButtonLazy = React.lazy(() => import('app2/Button'));

// A function to generate a color from a string
const getColorFromString = str => {
// Prime numbers used for generating a hash
let primes = [1, 2, 3, 5, 7, 11, 13, 17, 19, 23];
let hash = 0;

// Generate a hash from the string
for (let i = 0; i < str.length; i++) {
hash += str.charCodeAt(i) * primes[i % primes.length];
}

// Convert the hash to a color
let color = '#';
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
color += ('00' + value.toString(16)).substr(-2);
}

return color;
};

// The main App component
const App = () => (
<div>
<h1>Offline Remote</h1>
<h2>Remotes currently in use</h2>
{/* Display the names of the remotes loaded by the CustomPlugin */}
{__FEDERATION__.__INSTANCES__.map(inst => (
<span
style={{
padding: 10,
color: '#fff',
background: getColorFromString(inst.name.split().reverse().join('')),
}}
key={inst.name}
>
{inst.name}
</span>
))}
<p>
Click The second button. This will cause the <i>pick-remote.ts</i> to load remoteEntry urls
from a mock api call.
</p>
{/* LocalButton is a button component from the local app */}
<LocalButton />
{/* RemoteButton is a button component loaded from a remote app */}
<ErrorBoundary>
<React.Suspense fallback="Loading Button">
<RemoteButtonLazy />
</React.Suspense>
</ErrorBoundary>
{/* The Reset button clears the 'button' item from localStorage */}
<button
onClick={() => {
localStorage.clear('button');
window.location.reload();
}}
>
Reset{' '}
</button>
</div>
);

export default App;

class ErrorBoundary extends React.Component {
state = {
hasError: false,
};

componentDidCatch(error, errorInfo) {
console.error(error, errorInfo);
this.setState({ hasError: true });
}

render() {
const { children } = this.props;
if (this.state.hasError) {
return (
<div>
<h1>Something went wrong</h1>
<p>Try refreshing the page.</p>
</div>
);
}
return children;
}
}
11 changes: 11 additions & 0 deletions error-boundary/app1/src/Button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

const style = {
background: '#800',
color: '#fff',
padding: 12,
};

const Button = () => <button style={style}>App 1 Button</button>;

export default Button;
5 changes: 5 additions & 0 deletions error-boundary/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 error-boundary/app1/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import('./bootstrap');
66 changes: 66 additions & 0 deletions error-boundary/app1/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('@module-federation/enhanced');
const path = require('path');

// adds all your dependencies as shared modules
// version is inferred from package.json in the dependencies
// requiredVersion is used from your package.json
// dependencies will automatically use the highest available package
// in the federated app, based on version requirement in package.json
// multiple different versions might coexist in the federated app
// Note that this will not affect nested paths like "lodash/pluck"
// Note that this will disable some optimization on these packages
// with might lead the bundle size problems
const deps = require('./package.json').dependencies;

module.exports = {
entry: './src/index',
cache: false,
mode: 'development',
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
port: 3001,
},
target: 'web',
output: {
publicPath: 'auto',
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react'],
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
remotes: {
app2: 'app2@http://localhost:3002/remoteEntry.js',
},
exposes: {
'./Button': './src/Button',
},
shared: {
...deps,
react: {
singleton: true,
},
'react-dom': {
singleton: true,
},
},
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
32 changes: 32 additions & 0 deletions error-boundary/app2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "error-boundary_app2",
"version": "0.0.0",
"devDependencies": {
"@babel/core": "7.24.7",
"@babel/preset-react": "7.24.7",
"babel-loader": "9.1.3",
"html-webpack-plugin": "5.6.0",
"serve": "14.2.3",
"@rspack/core": "0.7.5",
"@rspack/cli": "0.7.5",
"@rspack/dev-server": "0.7.5",
"webpack": "5.92.1",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.0.4",
"@module-federation/runtime": "0.2.5",
"@module-federation/enhanced": "0.2.5"
},
"scripts": {
"start": "rspack serve -c rspack.config.js",
"build": "rspack build --mode production -c rspack.config.js",
"legacy:start": "webpack serve --config webpack.config.js",
"legacy:build": "webpack --config webpack.config.js --mode production",
"serve": "serve dist -p 3002",
"clean": "rm -rf dist"
},
"dependencies": {
"react": "^16.13.0",
"react-dom": "^16.13.0",
"lodash": "^3.10.1"
}
}
7 changes: 7 additions & 0 deletions error-boundary/app2/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head> </head>
<body>
<div id="root"></div>
</body>
</html>
Loading