Skip to content

Commit 059d911

Browse files
committed
Allow full-stack API calling
1 parent baaf6de commit 059d911

File tree

6 files changed

+103
-31
lines changed

6 files changed

+103
-31
lines changed

express.ts

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -50,38 +50,45 @@ app.use((req, res, next) => {
5050
return next();
5151
});
5252

53+
app.use("/.server-function", express.static("dist/server"));
54+
app.all(/^\/server-function\/(.*)/, async (req, res) => {
55+
const func = req.params[0];
56+
57+
const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
58+
59+
// Convert Express req -> Fetch Request
60+
const request = new Request(url, {
61+
method: req.method,
62+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
63+
headers: req.headers as any,
64+
body: ["GET", "HEAD"].includes(req.method)
65+
? null
66+
: JSON.stringify(req.body),
67+
});
68+
69+
const { loadAndCall } = await import("./preview/backend/load-remote.cjs");
70+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
71+
const response = await loadAndCall(
72+
func,
73+
request,
74+
pulseConfig.id,
75+
"http://localhost:3030",
76+
pulseConfig.version
77+
);
78+
79+
// If loadAndCall returns a Response (Fetch API Response)
80+
if (response instanceof Response) {
81+
res.status(response.status);
82+
response.headers.forEach((v, k) => res.setHeader(k, v));
83+
res.send(await response.text());
84+
} else {
85+
res.json(response);
86+
}
87+
});
88+
5389
if (isPreview) {
5490
/* Preview mode */
5591
app.use(express.static("dist/client"));
56-
app.use("/.server-function", express.static("dist/server"));
57-
app.all(/^\/server-function\/(.*)/, async (req, res) => {
58-
const func = req.params[0];
59-
60-
const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
61-
62-
// Convert Express req -> Fetch Request
63-
const request = new Request(url, {
64-
method: req.method,
65-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
66-
headers: req.headers as any,
67-
body: ["GET", "HEAD"].includes(req.method)
68-
? null
69-
: JSON.stringify(req.body),
70-
});
71-
72-
const { loadAndCall } = await import("./dist/preview/backend/index.cjs");
73-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
74-
const response = await loadAndCall(func, request);
75-
76-
// If loadAndCall returns a Response (Fetch API Response)
77-
if (response instanceof Response) {
78-
res.status(response.status);
79-
response.headers.forEach((v, k) => res.setHeader(k, v));
80-
res.send(await response.text());
81-
} else {
82-
res.json(response);
83-
}
84-
});
8592

8693
app.listen(3030, "0.0.0.0");
8794
} else if (isDev) {

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@module-federation/node": "2.7.17",
3131
"@module-federation/runtime": "0.19.1",
3232
"@tailwindcss/postcss": "^4.1.14",
33+
"@types/cors": "^2.8.19",
3334
"@types/react": "19.2.2",
3435
"@types/react-dom": "19.2.2",
3536
"concurrently": "^9.2.1",

preview/backend/load-remote.cjs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const { createInstance } = require("@module-federation/runtime");
2+
3+
async function loadAndCall(func, req, appId, origin, version) {
4+
// here we assign the return value of the init() function, which can be used to do some more complex
5+
// things with the module federation runtime
6+
const instance = createInstance({
7+
name: "server_function_runner",
8+
remotes: [
9+
{
10+
name: appId + "_server",
11+
entry: `${origin}/${appId}/${version}/server/remoteEntry.js`,
12+
},
13+
],
14+
});
15+
16+
const loadedFunc = (await instance.loadRemote(`${appId}_server/${func}`))
17+
.default;
18+
19+
const res = await loadedFunc(req);
20+
return res;
21+
}
22+
23+
module.exports = { loadAndCall };

src/main.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { useLoading } from "@pulse-editor/react-api";
55
export default function Main() {
66
const [count, setCount] = useState<number>(0);
77
const { isReady, toggleLoading } = useLoading();
8+
const [inputValue, setInputValue] = useState<string>("");
9+
const [apiResult, setApiResult] = useState<string>("");
810

911
useEffect(() => {
1012
if (isReady) {
@@ -38,10 +40,35 @@ export default function Main() {
3840
className="bg-gray-800 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded-sm"
3941
onClick={() => setCount(count + 1)}
4042
>
41-
Click me
43+
Click me to increase count
4244
</button>
4345
</div>
4446
<p className="text-blue-400">{count}</p>
47+
48+
<div>
49+
<input
50+
className="border-2 border-gray-300 rounded-sm p-2"
51+
type="text"
52+
value={inputValue}
53+
onChange={(e) => setInputValue(e.target.value)}
54+
/>
55+
<button
56+
className="bg-gray-800 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded-sm"
57+
onClick={() => {
58+
fetch("/server-function/echo", {
59+
method: "POST",
60+
headers: { "Content-Type": "application/json" },
61+
body: JSON.stringify({ message: inputValue }),
62+
}).then(async (response) => {
63+
const data = await response.json();
64+
setApiResult(data.message);
65+
});
66+
}}
67+
>
68+
Click me to call server function that echoes a message
69+
</button>
70+
<p className="text-blue-400">{apiResult}</p>
71+
</div>
4572
</div>
4673
);
4774
}

src/server-function/hello/hello-world.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ export default async function helloWorld(req: Request) {
77
return new Response("Method Not Allowed", { status: 405 });
88
}
99

10+
const params = new URL(req.url).searchParams;
11+
const name = params.get("name") ?? "world";
12+
1013
// Process the data and return a response
1114
return new Response(
1215
JSON.stringify({
13-
message: "Hello, world!",
16+
message: `Hello, ${name}!`,
1417
}),
1518
{ status: 200 }
1619
);

0 commit comments

Comments
 (0)