Skip to content

Commit 49c1a3a

Browse files
authored
feat: add React MF SharedWorker example (#4307)
1 parent 4b2b004 commit 49c1a3a

14 files changed

+197
-1
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@
175175
"vue3-demo",
176176
"vue3-demo/*",
177177
"vue3-demo-federation-with-vite",
178-
"vue3-demo-federation-with-vite/*"
178+
"vue3-demo-federation-with-vite/*",
179+
"react-sharedworker"
179180
]
180181
},
181182
"devDependencies": {

react-sharedworker/README.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# react-sharedworker
2+
Using Webpack Module Federation in SharedWorker
3+
4+
Federated module (defined under `module` folder), can be consumed by main thread in host application (defined under `host` folder), and it also can be consumed by worker thread.
5+
6+
## How to run
7+
8+
```
9+
yarn
10+
yarn start:module
11+
yarn start:host
12+
```
13+
14+
[Open browser](http://localhost:3000) and observe error in console.
15+
16+
![shared worker](./image.png)
17+
18+
## How to work
19+
20+
1. Use SharedWorker instead of Worker in host application
21+
2. Dynamically import bootstrap file in worker thread
22+
3. Use promise based dynamic remotes `host/webpack.host-config.js`
23+
4. Use multiple entry points in webpack config `module/webpack.module-config.js`

react-sharedworker/host/index.html

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<body>
4+
open console to see the error
5+
</body>
6+
</html>
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { testValue } from 'myModule/testValue';
2+
3+
console.log('testValue in main thread', testValue);
4+
5+
const worker = new SharedWorker(new URL('./worker', import.meta.url));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { testValue } from 'myModule/testValue';
2+
3+
console.log('testValue in worker thread', testValue);

react-sharedworker/host/src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import('./bootstrap');

react-sharedworker/host/src/worker.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import('./bootstrapWorker');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const path = require('path');
2+
const HtmlWebpackPlugin = require('html-webpack-plugin');
3+
const { container } = require('webpack');
4+
5+
module.exports = {
6+
mode: 'development',
7+
entry: path.resolve(__dirname, 'src/index.js'),
8+
output: {
9+
path: path.resolve(__dirname, 'build'),
10+
filename: 'host-bundle-[name].js',
11+
chunkFilename: 'host-chunk-[name].js',
12+
},
13+
plugins: [
14+
new container.ModuleFederationPlugin({
15+
remotes: {
16+
myModule: `promise new Promise(resolve => {
17+
// This part depends on how you plan on hosting and versioning your federated modules
18+
if (globalThis.SharedWorkerGlobalScope) {
19+
const remoteUrlWithVersion = 'http://localhost:3001/worker/remoteEntry.js'
20+
importScripts(remoteUrlWithVersion)
21+
resolve(globalThis.myModule);
22+
return;
23+
}
24+
const remoteUrlWithVersion = 'http://localhost:3001/remoteEntry.js'
25+
const script = document.createElement('script')
26+
script.src = remoteUrlWithVersion
27+
script.onload = () => {
28+
// the injected script has loaded and is available on window
29+
// we can now resolve this Promise
30+
const proxy = {
31+
get: (request) => window.myModule.get(request),
32+
init: (...arg) => {
33+
try {
34+
return window.myModule.init(...arg)
35+
} catch(e) {
36+
console.log('remote container already initialized')
37+
}
38+
}
39+
}
40+
resolve(proxy)
41+
}
42+
// inject this script with the src set to the versioned remoteEntry.js
43+
document.head.appendChild(script);
44+
})
45+
`,
46+
},
47+
}),
48+
new HtmlWebpackPlugin({
49+
template: path.resolve(__dirname, 'index.html'),
50+
}),
51+
],
52+
devServer: {
53+
static: {
54+
directory: path.join(__dirname, 'build'),
55+
},
56+
compress: true,
57+
port: 3000,
58+
}
59+
};

react-sharedworker/image.png

421 KB
Loading

react-sharedworker/module/dev.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const express = require("express");
2+
const webpack = require("webpack");
3+
const webpackDevMiddleware = require("webpack-dev-middleware");
4+
5+
const app = express();
6+
const [config1, config2] = require("./webpack.module-config.js");
7+
8+
const compiler1 = webpack(config1);
9+
const compiler2 = webpack(config2);
10+
11+
app.use(
12+
webpackDevMiddleware(compiler1, {
13+
publicPath: config1.output.publicPath + "worker/",
14+
})
15+
);
16+
17+
app.use(
18+
webpackDevMiddleware(compiler2, {
19+
publicPath: config2.output.publicPath,
20+
})
21+
);
22+
23+
app.listen(3001, function () {
24+
console.log("App listening on port 3001!\n");
25+
});
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './testValue';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const testValue = 'module federation test value';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const path = require("path");
2+
const { container } = require("webpack");
3+
4+
module.exports = [
5+
{
6+
target: "webworker",
7+
mode: "development",
8+
entry: path.resolve(__dirname, "src/index.js"),
9+
output: {
10+
path: path.resolve(__dirname, "build/worker"),
11+
filename: "module-bundle-[name].js",
12+
chunkFilename: "module-chunk-[name].js",
13+
publicPath: "http://localhost:3001/",
14+
},
15+
plugins: [
16+
new container.ModuleFederationPlugin({
17+
name: "myModule",
18+
filename: "remoteEntry.js",
19+
exposes: {
20+
"./testValue": path.resolve(__dirname, "src/testValue.js"),
21+
},
22+
}),
23+
],
24+
},
25+
{
26+
mode: "development",
27+
entry: path.resolve(__dirname, "src/index.js"),
28+
output: {
29+
path: path.resolve(__dirname, 'build'),
30+
filename: "module-bundle-[name].js",
31+
chunkFilename: "module-chunk-[name].js",
32+
publicPath: "http://localhost:3001/",
33+
},
34+
plugins: [
35+
new container.ModuleFederationPlugin({
36+
name: "myModule",
37+
filename: "remoteEntry.js",
38+
exposes: {
39+
"./testValue": path.resolve(__dirname, "src/testValue.js"),
40+
},
41+
}),
42+
],
43+
},
44+
];

react-sharedworker/package.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "react-sharedworker",
3+
"private": true,
4+
"scripts": {
5+
"build:host": "webpack --node-env=production --progress --config ./host/webpack.host-config.js",
6+
"build:module": "webpack --node-env=production --progress --config ./module/webpack.module-config.js",
7+
"serve:host": "npx http-server ./host/build -p 3000",
8+
"serve:module": "npx http-server ./module/build -p 3001",
9+
"start:host": "npm run build:host && npm run serve:host",
10+
"start:module": "npm run build:module && npm run serve:module",
11+
"clean": "rimraf ./host/build ./module/build",
12+
"dev:host": "webpack serve --config ./host/webpack.host-config.js",
13+
"dev:module": "node ./module/dev.js",
14+
"dev": "yarn dev:host & yarn dev:module"
15+
},
16+
"devDependencies": {
17+
"express": "^4.18.2",
18+
"html-webpack-plugin": "5.6.0",
19+
"rimraf": "^5.0.5",
20+
"webpack": "5.95.0",
21+
"webpack-cli": "5.1.4",
22+
"webpack-dev-middleware": "^7.4.2",
23+
"webpack-dev-server": "5.0.4"
24+
},
25+
"license": "MIT"
26+
}

0 commit comments

Comments
 (0)