Skip to content

Commit e99673b

Browse files
committed
Working example at loading remote at runtime, without synchronous imports
1 parent a9e616d commit e99673b

File tree

18 files changed

+27993
-0
lines changed

18 files changed

+27993
-0
lines changed

advanced-api/dynamic-remotes-synchronous-imports/app2/package-lock.json

Lines changed: 13764 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@dynamic-remotes/app2",
3+
"version": "0.0.0",
4+
"private": true,
5+
"devDependencies": {
6+
"@babel/core": "7.12.3",
7+
"@babel/preset-react": "7.12.1",
8+
"babel-loader": "8.1.0",
9+
"html-webpack-plugin": "4.5.0",
10+
"serve": "11.3.2",
11+
"webpack": "5.6.0",
12+
"webpack-cli": "4.3.0",
13+
"webpack-dev-server": "3.11.0"
14+
},
15+
"scripts": {
16+
"start": "webpack-cli serve",
17+
"build": "webpack --mode production",
18+
"serve": "serve dist -p 3002",
19+
"clean": "rm -rf dist"
20+
},
21+
"dependencies": {
22+
"moment": "^2.24.0",
23+
"react": "^16.13.0",
24+
"react-dom": "^16.13.0"
25+
}
26+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<html>
2+
<body>
3+
<div id="root"></div>
4+
</body>
5+
</html>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { loadFromRemote } from "./loader";
2+
import Widget from "./Widget";
3+
import React, { lazy, Suspense } from "react";
4+
5+
const WidgetRemote = lazy(async () => {
6+
const component = await loadFromRemote({
7+
component: "Widget", remote: {
8+
url: "http://localhost:3003/remoteEntry.js",
9+
name: "app3",
10+
},
11+
});
12+
return component();
13+
});
14+
15+
const App = () => (
16+
<div>
17+
<h1>Dynamic System Host</h1>
18+
<h2>App 2</h2>
19+
<Widget />
20+
<Suspense fallback="Loading widget">
21+
<WidgetRemote />
22+
</Suspense>
23+
</div>
24+
);
25+
26+
export default App;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from "react";
2+
import moment from "moment";
3+
4+
export default function Widget() {
5+
return (
6+
<div
7+
style={{
8+
borderRadius: "4px",
9+
padding: "2em",
10+
backgroundColor: "red",
11+
color: "white",
12+
}}
13+
>
14+
<h2>App 2 Widget</h2>
15+
<p>
16+
Moment shouldn't download twice, the host has no moment.js <br />{" "}
17+
{moment().format("MMMM Do YYYY, h:mm:ss a")}
18+
</p>
19+
</div>
20+
);
21+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import App from "./App";
2+
import React from "react";
3+
import ReactDOM from "react-dom";
4+
5+
ReactDOM.render(<App />, document.getElementById("root"));
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import("./bootstrap");
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Modified from: https://github.com/webpack/webpack/issues/11033
2+
3+
const scriptsCache = {};
4+
5+
export const loadScript = async (src) => {
6+
if (scriptsCache[src]) {
7+
return scriptsCache[src];
8+
}
9+
10+
scriptsCache[src] = new Promise((resolve, reject) => {
11+
const { document } = window;
12+
13+
const script = document.createElement("script");
14+
15+
script.setAttribute("type", "text/javascript");
16+
script.setAttribute("src", src);
17+
18+
script.addEventListener("error", (err) => {
19+
scriptsCache[src] = null;
20+
reject(err);
21+
});
22+
23+
script.addEventListener("load", () => {
24+
resolve(script);
25+
});
26+
27+
document.head.appendChild(script);
28+
});
29+
return scriptsCache[src];
30+
};
31+
32+
export const loadAndInitiateWebpackContainer = async (
33+
remote = { name: "", url: "" }
34+
) => {
35+
const { name, url } = remote;
36+
37+
await loadScript(url);
38+
39+
// Initializes the share scope. This fills it with known provided modules from
40+
// this build and all remotes
41+
await __webpack_init_sharing__("default");
42+
const container = window[name]; // or get the container somewhere else
43+
44+
if (!container || !container.init) {
45+
throw new Error(`Cannot load external remote: ${name} from url: ${url}`);
46+
}
47+
48+
// Initialize the container, it may provide shared modules
49+
await container.init(__webpack_share_scopes__.default);
50+
51+
return container;
52+
};
53+
54+
export const loadFromRemote = ({
55+
remote = { name: "", url: "" },
56+
component = "",
57+
} = {}) => {
58+
const { name, url } = remote;
59+
60+
if (!url) {
61+
throw new Error("Missing remote url");
62+
}
63+
if (!name) {
64+
throw new Error("Missing remote name");
65+
}
66+
if (!component) {
67+
throw new Error("Missing component");
68+
}
69+
70+
return async () => {
71+
const container = await loadAndInitiateWebpackContainer({ url, name });
72+
73+
if (!container.get)
74+
throw new Error(`Cannot load external remote: ${name} from url: ${url}`);
75+
76+
component = component.match(/^\.\//) ? component : `./${component}`;
77+
78+
const factory = await container.get(component);
79+
80+
if (!factory)
81+
throw new Error(
82+
`Cannot load ${component} in remote: ${name} from url ${url}`
83+
);
84+
85+
return factory();
86+
};
87+
};
88+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
const HtmlWebpackPlugin = require("html-webpack-plugin");
2+
const { ModuleFederationPlugin } = require("webpack").container;
3+
const path = require("path");
4+
const deps = require("./package.json").dependencies;
5+
module.exports = {
6+
entry: "./src/index",
7+
mode: "development",
8+
target: "web",
9+
devServer: {
10+
contentBase: path.join(__dirname, "dist"),
11+
port: 3002,
12+
},
13+
output: {
14+
publicPath: "auto",
15+
},
16+
module: {
17+
rules: [
18+
{
19+
test: /\.jsx?$/,
20+
loader: "babel-loader",
21+
exclude: /node_modules/,
22+
options: {
23+
presets: ["@babel/preset-react"],
24+
},
25+
},
26+
],
27+
},
28+
plugins: [
29+
new ModuleFederationPlugin({
30+
name: "app2",
31+
filename: "remoteEntry.js",
32+
exposes: {
33+
"./Widget": "./src/Widget",
34+
},
35+
shared: {
36+
moment: deps.moment,
37+
react: {
38+
requiredVersion: deps.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+
"react-dom": {
45+
requiredVersion: deps["react-dom"],
46+
singleton: true, // only a single version of the shared module is allowed
47+
},
48+
},
49+
}),
50+
new HtmlWebpackPlugin({
51+
template: "./public/index.html",
52+
}),
53+
],
54+
};

0 commit comments

Comments
 (0)